/* * Copyright 2004 James Bursa * Copyright 2005 Richard Wilson * Copyright 2008 Adrian Lees * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * \file * RISC OS implementation of bitmap operations. * * This implements the interface given by image/bitmap.h using RISC OS * sprites. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "utils/nsoption.h" #include "utils/filename.h" #include "utils/log.h" #include "utils/messages.h" #include "netsurf/plotters.h" #include "netsurf/bitmap.h" #include "netsurf/content.h" #include "riscos/gui.h" #include "riscos/image.h" #include "riscos/palettes.h" #include "riscos/content-handlers/sprite.h" #include "riscos/tinct.h" #include "riscos/bitmap.h" /** Colour in the overlay sprite that allows the bitmap to show through */ #define OVERLAY_INDEX 0xfe /** Size of buffer used when constructing mask data to be saved */ #define SAVE_CHUNK_SIZE 4096 /** * Whether we can use 32bpp sprites */ static int thumbnail_32bpp_available = -1; /** * Sprite output context saving */ struct thumbnail_save_area { osspriteop_save_area *save_area; int context1; int context2; int context3; }; /** * Initialise a bitmaps sprite area. * * \param bitmap the bitmap to initialise * \return true if bitmap initialised else false. */ static bool bitmap_initialise(struct bitmap *bitmap) { unsigned int area_size; osspriteop_area *sprite_area; osspriteop_header *sprite; assert(!bitmap->sprite_area); area_size = 16 + 44 + bitmap->width * bitmap->height * 4; if (bitmap->state & BITMAP_CLEAR_MEMORY) bitmap->sprite_area = calloc(1, area_size); else bitmap->sprite_area = malloc(area_size); if (!bitmap->sprite_area) return false; /* area control block */ sprite_area = bitmap->sprite_area; sprite_area->size = area_size; sprite_area->sprite_count = 1; sprite_area->first = 16; sprite_area->used = area_size; /* sprite control block */ sprite = (osspriteop_header *) (sprite_area + 1); sprite->size = area_size - 16; memset(sprite->name, 0x00, 12); strncpy(sprite->name, "bitmap", 12); sprite->width = bitmap->width - 1; sprite->height = bitmap->height - 1; sprite->left_bit = 0; sprite->right_bit = 31; sprite->image = sprite->mask = 44; sprite->mode = tinct_SPRITE_MODE; return true; } /* exported interface documented in riscos/bitmap.h */ void *riscos_bitmap_create(int width, int height, unsigned int state) { struct bitmap *bitmap; if (width == 0 || height == 0) return NULL; bitmap = calloc(1, sizeof(struct bitmap)); if (!bitmap) return NULL; bitmap->width = width; bitmap->height = height; bitmap->state = state; return bitmap; } /* exported interface documented in riscos/bitmap.h */ unsigned char *riscos_bitmap_get_buffer(void *vbitmap) { struct bitmap *bitmap = (struct bitmap *) vbitmap; assert(bitmap); /* dynamically create the buffer */ if (bitmap->sprite_area == NULL) { if (!bitmap_initialise(bitmap)) return NULL; } /* image data area should exist */ if (bitmap->sprite_area) return ((unsigned char *) (bitmap->sprite_area)) + 16 + 44; return NULL; } /** * Sets whether a bitmap should be plotted opaque * * \param vbitmap a bitmap, as returned by bitmap_create() * \param opaque whether the bitmap should be plotted opaque */ static void bitmap_set_opaque(void *vbitmap, bool opaque) { struct bitmap *bitmap = (struct bitmap *) vbitmap; assert(bitmap); if (opaque) bitmap->state |= BITMAP_OPAQUE; else bitmap->state &= ~BITMAP_OPAQUE; } /** * Find the width of a pixel row in bytes. * * \param vbitmap A bitmap, as returned by riscos_bitmap_create() * \return width of a pixel row in the bitmap */ static size_t bitmap_get_rowstride(void *vbitmap) { struct bitmap *bitmap = (struct bitmap *) vbitmap; return bitmap->width * 4; } /** * Tests whether a bitmap has an opaque alpha channel * * \param vbitmap a bitmap, as returned by bitmap_create() * \return whether the bitmap is opaque */ static bool bitmap_test_opaque(void *vbitmap) { struct bitmap *bitmap = (struct bitmap *) vbitmap; unsigned char *sprite; unsigned int width, height, size; osspriteop_header *sprite_header; unsigned *p, *ep; assert(bitmap); sprite = riscos_bitmap_get_buffer(bitmap); if (!sprite) return false; width = bitmap_get_rowstride(bitmap); sprite_header = (osspriteop_header *) (bitmap->sprite_area + 1); height = (sprite_header->height + 1); size = width * height; p = (void *) sprite; ep = (void *) (sprite + (size & ~31)); while (p < ep) { /* \todo prefetch(p, 128)? */ if (((p[0] & p[1] & p[2] & p[3] & p[4] & p[5] & p[6] & p[7]) & 0xff000000U) != 0xff000000U) return false; p += 8; } ep = (void *) (sprite + size); while (p < ep) { if ((*p & 0xff000000U) != 0xff000000U) return false; p++; } return true; } /* exported interface documented in riscos/bitmap.h */ bool riscos_bitmap_get_opaque(void *vbitmap) { struct bitmap *bitmap = (struct bitmap *) vbitmap; assert(bitmap); return (bitmap->state & BITMAP_OPAQUE); } /* exported interface documented in riscos/bitmap.h */ void riscos_bitmap_destroy(void *vbitmap) { struct bitmap *bitmap = (struct bitmap *) vbitmap; assert(bitmap); /* destroy bitmap */ if (bitmap->sprite_area) { free(bitmap->sprite_area); } free(bitmap); } /* exported interface documented in riscos/bitmap.h */ bool riscos_bitmap_save(void *vbitmap, const char *path, unsigned flags) { struct bitmap *bitmap = (struct bitmap *) vbitmap; os_error *error; if (bitmap == NULL) { ro_warn_user("SaveError", messages_get("SprIsNull")); return false; } if (!bitmap->sprite_area) { riscos_bitmap_get_buffer(bitmap); } if (!bitmap->sprite_area) return false; if (riscos_bitmap_get_opaque(bitmap)) { error = xosspriteop_save_sprite_file(osspriteop_USER_AREA, (bitmap->sprite_area), path); if (error) { LOG("xosspriteop_save_sprite_file: 0x%x: %s", error->errnum, error->errmess); ro_warn_user("SaveError", error->errmess); return false; } return true; } else { /* to make the saved sprite useful we must convert from 'Tinct' * format to either a bi-level mask or a Select-style full * alpha channel */ osspriteop_area *area = bitmap->sprite_area; osspriteop_header *hdr = (void *) ((char *) area + area->first); unsigned width = hdr->width + 1, height = hdr->height + 1; unsigned image_size = height * width * 4; unsigned char *chunk_buf; unsigned *p, *elp, *eip; unsigned mask_size; size_t chunk_pix; struct { osspriteop_area area; osspriteop_header hdr; } file_hdr; os_fw fw; /* we only support 32bpp sprites */ if ((((unsigned)hdr->mode >> 27)&15) != 6) { assert(!"Unsupported sprite format in bitmap_save"); return false; } chunk_buf = malloc(SAVE_CHUNK_SIZE); if (!chunk_buf) { ro_warn_user("NoMemory", NULL); return false; } file_hdr.area = *area; file_hdr.hdr = *hdr; if (flags & BITMAP_SAVE_FULL_ALPHA) { mask_size = ((width + 3) & ~3) * height; chunk_pix = SAVE_CHUNK_SIZE; file_hdr.hdr.mode = (os_mode)((unsigned)file_hdr.hdr.mode | (1U<<31)); } else { mask_size = (((width + 31) & ~31)/8) * height; chunk_pix = SAVE_CHUNK_SIZE<<3; file_hdr.hdr.mode = (os_mode)((unsigned)file_hdr.hdr.mode & ~(1U<<31)); } file_hdr.area.sprite_count = 1; file_hdr.area.first = sizeof(file_hdr.area); file_hdr.area.used = sizeof(file_hdr) + image_size + mask_size; file_hdr.hdr.image = sizeof(file_hdr.hdr); file_hdr.hdr.mask = file_hdr.hdr.image + image_size; file_hdr.hdr.size = file_hdr.hdr.mask + mask_size; error = xosfind_openoutw(0, path, NULL, &fw); if (error) { LOG("xosfind_openoutw: 0x%x: %s", error->errnum, error->errmess); free(chunk_buf); ro_warn_user("SaveError", error->errmess); return false; } p = (void *) ((char *) hdr + hdr->image); /* write out the area header, sprite header and image data */ error = xosgbpb_writew(fw, (byte*)&file_hdr + 4, sizeof(file_hdr)-4, NULL); if (!error) error = xosgbpb_writew(fw, (byte*)p, image_size, NULL); if (error) { LOG("xosgbpb_writew: 0x%x: %s", error->errnum, error->errmess); free(chunk_buf); xosfind_closew(fw); ro_warn_user("SaveError", error->errmess); return false; } /* then write out the mask data in chunks */ eip = p + (width * height); /* end of image */ elp = p + width; /* end of line */ while (p < eip) { unsigned char *dp = chunk_buf; unsigned *ep = p + chunk_pix; if (ep > elp) ep = elp; if (flags & BITMAP_SAVE_FULL_ALPHA) { while (p < ep) { *dp++ = ((unsigned char*)p)[3]; p++; } } else { unsigned char mb = 0; int msh = 0; while (p < ep) { if (((unsigned char*)p)[3]) mb |= (1 << msh); if (++msh >= 8) { *dp++ = mb; msh = 0; mb = 0; } p++; } if (msh > 0) *dp++ = mb; } if (p >= elp) { /* end of line yet? */ /* align to word boundary */ while ((int)dp & 3) *dp++ = 0; /* advance end of line pointer */ elp += width; } error = xosgbpb_writew(fw, (byte*)chunk_buf, dp-chunk_buf, NULL); if (error) { LOG("xosgbpb_writew: 0x%x: %s", error->errnum, error->errmess); free(chunk_buf); xosfind_closew(fw); ro_warn_user("SaveError", error->errmess); return false; } } error = xosfind_closew(fw); if (error) { LOG("xosfind_closew: 0x%x: %s", error->errnum, error->errmess); ro_warn_user("SaveError", error->errmess); } error = xosfile_set_type(path, osfile_TYPE_SPRITE); if (error) { LOG("xosfile_set_type: 0x%x: %s", error->errnum, error->errmess); ro_warn_user("SaveError", error->errmess); } free(chunk_buf); return true; } } /** * The bitmap image has changed, so flush any persistent cache. * * \param vbitmap a bitmap, as returned by bitmap_create() */ static void bitmap_modified(void *vbitmap) { struct bitmap *bitmap = (struct bitmap *) vbitmap; bitmap->state |= BITMAP_MODIFIED; } /** * Get the width of a bitmap. * * \param vbitmap A bitmap, as returned by bitmap_create() * \return The bitmaps width in pixels. */ static int bitmap_get_width(void *vbitmap) { struct bitmap *bitmap = (struct bitmap *) vbitmap; return bitmap->width; } /** * Get the height of a bitmap. * * \param vbitmap A bitmap, as returned by bitmap_create() * \return The bitmaps height in pixels. */ static int bitmap_get_height(void *vbitmap) { struct bitmap *bitmap = (struct bitmap *) vbitmap; return bitmap->height; } /** * Find the bytes per pixel of a bitmap * * \param vbitmap a bitmap, as returned by bitmap_create() * \return bytes per pixel */ static size_t bitmap_get_bpp(void *vbitmap) { struct bitmap *bitmap = (struct bitmap *)vbitmap; assert(bitmap); return 4; } /* exported interface documented in riscos/bitmap.h */ void riscos_bitmap_overlay_sprite(struct bitmap *bitmap, const osspriteop_header *s) { const os_colour *palette; const byte *sp, *mp; bool masked = false; bool alpha = false; os_error *error; int dp_offset; int sp_offset; unsigned *dp; int x, y; int w, h; assert(sprite_bpp(s) == 8); if ((unsigned)s->mode & 0x80000000U) alpha = true; error = xosspriteop_read_sprite_info(osspriteop_PTR, (osspriteop_area *)0x100, (osspriteop_id)s, &w, &h, NULL, NULL); if (error) { LOG("xosspriteop_read_sprite_info: 0x%x:%s", error->errnum, error->errmess); return; } sp_offset = ((s->width + 1) * 4) - w; if (w > bitmap->width) w = bitmap->width; if (h > bitmap->height) h = bitmap->height; dp_offset = bitmap_get_rowstride(bitmap) / 4; dp = (void*)riscos_bitmap_get_buffer(bitmap); if (!dp) return; sp = (byte*)s + s->image; mp = (byte*)s + s->mask; sp += s->left_bit / 8; mp += s->left_bit / 8; if (s->image > (int)sizeof(*s)) palette = (os_colour*)(s + 1); else palette = default_palette8; if (s->mask != s->image) { masked = true; bitmap_set_opaque(bitmap, false); } /* (partially-)transparent pixels in the overlayed sprite retain * their transparency in the output bitmap; opaque sprite pixels * are also propagated to the bitmap, except those which are the * OVERLAY_INDEX colour which allow the original bitmap contents to * show through */ for (y = 0; y < h; y++) { unsigned *sdp = dp; for(x = 0; x < w; x++) { os_colour d = ((unsigned)palette[(*sp) << 1]) >> 8; if (*sp++ == OVERLAY_INDEX) d = *dp; if (masked) { if (alpha) d |= ((*mp << 24) ^ 0xff000000U); else if (*mp) d |= 0xff000000U; } *dp++ = d; mp++; } dp = sdp + dp_offset; sp += sp_offset; mp += sp_offset; } } /** * Creates an 8bpp canvas. * * \param bitmap the bitmap to clone the size of * \return a sprite area containing an 8bpp sprite */ static osspriteop_area *thumbnail_create_8bpp(struct bitmap *bitmap) { unsigned image_size = ((bitmap->width + 3) & ~3) * bitmap->height; bool opaque = riscos_bitmap_get_opaque(bitmap); osspriteop_header *sprite_header = NULL; osspriteop_area *sprite_area = NULL; unsigned area_size; /* clone the sprite */ area_size = sizeof(osspriteop_area) + sizeof(osspriteop_header) + image_size + 2048; if (!opaque) area_size += image_size; sprite_area = (osspriteop_area *)malloc(area_size); if (!sprite_area) { LOG("no memory for malloc()"); return NULL; } sprite_area->size = area_size; sprite_area->sprite_count = 1; sprite_area->first = 16; sprite_area->used = area_size; sprite_header = (osspriteop_header *)(sprite_area + 1); sprite_header->size = area_size - sizeof(osspriteop_area); memset(sprite_header->name, 0x00, 12); strcpy(sprite_header->name, "bitmap"); sprite_header->left_bit = 0; sprite_header->height = bitmap->height - 1; sprite_header->mode = os_MODE8BPP90X90; sprite_header->right_bit = ((bitmap->width << 3) - 1) & 31; sprite_header->width = ((bitmap->width + 3) >> 2) - 1; sprite_header->image = sizeof(osspriteop_header) + 2048; sprite_header->mask = sizeof(osspriteop_header) + 2048; if (!opaque) sprite_header->mask += image_size; /* create the palette. we don't read the necessary size like * we really should as we know it's going to have 256 entries * of 8 bytes = 2048. */ xcolourtrans_read_palette((osspriteop_area *)os_MODE8BPP90X90, (osspriteop_id)0, (os_palette *)(sprite_header + 1), 2048, (colourtrans_palette_flags)(1 << 1), 0); return sprite_area; } /** * Switches output to the specified sprite and returns the previous context. */ static struct thumbnail_save_area* thumbnail_switch_output(osspriteop_area *sprite_area, osspriteop_header *sprite_header) { struct thumbnail_save_area *save_area; int size; /* create a save area */ save_area = calloc(sizeof(struct thumbnail_save_area), 1); if (save_area == NULL) return NULL; /* allocate OS_SpriteOp save area */ if (xosspriteop_read_save_area_size(osspriteop_PTR, sprite_area, (osspriteop_id)sprite_header, &size)) { free(save_area); return NULL; } /* create the save area */ save_area->save_area = malloc((unsigned)size); if (save_area->save_area == NULL) { free(save_area); return NULL; } save_area->save_area->a[0] = 0; /* switch output to sprite */ if (xosspriteop_switch_output_to_sprite(osspriteop_PTR, sprite_area, (osspriteop_id)sprite_header, save_area->save_area, 0, &save_area->context1, &save_area->context2, &save_area->context3)) { free(save_area->save_area); free(save_area); return NULL; } return save_area; } /** * Restores output to the specified context, and destroys it. */ static void thumbnail_restore_output(struct thumbnail_save_area *save_area) { /* we don't care if we err, as there's nothing we can do about it */ xosspriteop_switch_output_to_sprite(osspriteop_PTR, (osspriteop_area *)save_area->context1, (osspriteop_id)save_area->context2, (osspriteop_save_area *)save_area->context3, 0, 0, 0, 0); free(save_area->save_area); free(save_area); } /** * Convert a bitmap to 8bpp. * * \param bitmap the bitmap to convert * \return a sprite area containing an 8bpp sprite */ osspriteop_area *riscos_bitmap_convert_8bpp(struct bitmap *bitmap) { struct thumbnail_save_area *save_area; osspriteop_area *sprite_area = NULL; osspriteop_header *sprite_header = NULL; sprite_area = thumbnail_create_8bpp(bitmap); if (!sprite_area) return NULL; sprite_header = (osspriteop_header *)(sprite_area + 1); /* switch output and redraw */ save_area = thumbnail_switch_output(sprite_area, sprite_header); if (save_area == NULL) { if (thumbnail_32bpp_available != 1) free(sprite_area); return false; } _swix(Tinct_Plot, _IN(2) | _IN(3) | _IN(4) | _IN(7), (osspriteop_header *)(bitmap->sprite_area + 1), 0, 0, tinct_ERROR_DIFFUSE); thumbnail_restore_output(save_area); if (sprite_header->image != sprite_header->mask) { /* build the sprite mask from the alpha channel */ void *buf = riscos_bitmap_get_buffer(bitmap); unsigned *dp = (unsigned *) buf; if (!dp) return sprite_area; int w = bitmap_get_width(bitmap); int h = bitmap_get_height(bitmap); int dp_offset = bitmap_get_rowstride(bitmap) / 4 - w; int mp_offset = ((sprite_header->width + 1) * 4) - w; byte *mp = (byte*)sprite_header + sprite_header->mask; bool alpha = ((unsigned)sprite_header->mode & 0x80000000U) != 0; while (h-- > 0) { int x = 0; for(x = 0; x < w; x++) { unsigned d = *dp++; if (alpha) *mp++ = (d >> 24) ^ 0xff; else *mp++ = (d < 0xff000000U) ? 0 : 0xff; } dp += dp_offset; mp += mp_offset; } } return sprite_area; } /** * Check to see whether 32bpp sprites are available. * * Rather than using Wimp_ReadSysInfo we test if 32bpp sprites are available * in case the user has a 3rd party patch to enable them. */ static void thumbnail_test(void) { unsigned int area_size; osspriteop_area *sprite_area; /* try to create a 1x1 32bpp sprite */ area_size = sizeof(osspriteop_area) + sizeof(osspriteop_header) + sizeof(int); if ((sprite_area = (osspriteop_area *)malloc(area_size)) == NULL) { LOG("Insufficient memory to perform sprite test."); return; } sprite_area->size = area_size + 1; sprite_area->sprite_count = 0; sprite_area->first = 16; sprite_area->used = 16; if (xosspriteop_create_sprite(osspriteop_NAME, sprite_area, "test", false, 1, 1, (os_mode)tinct_SPRITE_MODE)) thumbnail_32bpp_available = 0; else thumbnail_32bpp_available = 1; free(sprite_area); } /* exported interface documented in riscos/bitmap.h */ nserror riscos_bitmap_render(struct bitmap *bitmap, struct hlcache_handle *content) { struct thumbnail_save_area *save_area; osspriteop_area *sprite_area = NULL; osspriteop_header *sprite_header = NULL; struct redraw_context ctx = { .interactive = false, .background_images = true, .plot = &ro_plotters }; assert(content); assert(bitmap); LOG("content %p in bitmap %p", content, bitmap); /* check if we have access to 32bpp sprites natively */ if (thumbnail_32bpp_available == -1) { thumbnail_test(); } /* if we don't support 32bpp sprites then we redirect to an 8bpp * image and then convert back. */ if (thumbnail_32bpp_available != 1) { sprite_area = thumbnail_create_8bpp(bitmap); if (!sprite_area) return false; sprite_header = (osspriteop_header *)(sprite_area + 1); } else { const uint8_t *pixbufp = riscos_bitmap_get_buffer(bitmap); if (!pixbufp || !bitmap->sprite_area) return false; sprite_area = bitmap->sprite_area; sprite_header = (osspriteop_header *)(sprite_area + 1); } /* set up the plotters */ ro_plot_origin_x = 0; ro_plot_origin_y = bitmap->height * 2; /* switch output and redraw */ save_area = thumbnail_switch_output(sprite_area, sprite_header); if (!save_area) { if (thumbnail_32bpp_available != 1) free(sprite_area); return false; } rufl_invalidate_cache(); colourtrans_set_gcol(os_COLOUR_WHITE, colourtrans_SET_BG_GCOL, os_ACTION_OVERWRITE, 0); /* render the content */ content_scaled_redraw(content, bitmap->width, bitmap->height, &ctx); thumbnail_restore_output(save_area); rufl_invalidate_cache(); /* if we changed to 8bpp then go back to 32bpp */ if (thumbnail_32bpp_available != 1) { const uint8_t *pixbufp = riscos_bitmap_get_buffer(bitmap); _kernel_oserror *error; if (!pixbufp || !bitmap->sprite_area) { free(sprite_area); return false; } error = _swix(Tinct_ConvertSprite, _INR(2,3), sprite_header, (osspriteop_header *)(bitmap->sprite_area + 1)); free(sprite_area); if (error) return false; } bitmap_modified(bitmap); return NSERROR_OK; } static struct gui_bitmap_table bitmap_table = { .create = riscos_bitmap_create, .destroy = riscos_bitmap_destroy, .set_opaque = bitmap_set_opaque, .get_opaque = riscos_bitmap_get_opaque, .test_opaque = bitmap_test_opaque, .get_buffer = riscos_bitmap_get_buffer, .get_rowstride = bitmap_get_rowstride, .get_width = bitmap_get_width, .get_height = bitmap_get_height, .get_bpp = bitmap_get_bpp, .save = riscos_bitmap_save, .modified = bitmap_modified, .render = riscos_bitmap_render, }; struct gui_bitmap_table *riscos_bitmap_table = &bitmap_table;