1203 lines
30 KiB
C
1203 lines
30 KiB
C
/*
|
|
* Copyright 2004 James Bursa <bursa@users.sourceforge.net>
|
|
* Copyright 2005 Richard Wilson <info@tinct.net>
|
|
* Copyright 2008 Adrian Lees <adrianl@users.sourceforge.net>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/** \file
|
|
* Generic bitmap handling (RISC OS implementation).
|
|
*
|
|
* This implements the interface given by desktop/bitmap.h using RISC OS
|
|
* sprites.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <swis.h>
|
|
#include <unixlib/local.h>
|
|
#include "oslib/osfile.h"
|
|
#include "oslib/osfind.h"
|
|
#include "oslib/osgbpb.h"
|
|
#include "oslib/osspriteop.h"
|
|
#include "oslib/wimp.h"
|
|
#include "content/content.h"
|
|
#include "image/bitmap.h"
|
|
#include "riscos/bitmap.h"
|
|
#include "riscos/image.h"
|
|
#include "riscos/options.h"
|
|
#include "riscos/palettes.h"
|
|
#include "riscos/content-handlers/sprite.h"
|
|
#include "riscos/tinct.h"
|
|
#include "utils/filename.h"
|
|
#include "utils/log.h"
|
|
#include "utils/utils.h"
|
|
|
|
/** Colour in the overlay sprite that allows the bitmap to show through */
|
|
#define OVERLAY_INDEX 0xfe
|
|
|
|
#define MAINTENANCE_THRESHOLD 32
|
|
|
|
/** Size of buffer used when constructing mask data to be saved */
|
|
#define SAVE_CHUNK_SIZE 4096
|
|
|
|
/** The head of the bitmap list
|
|
*/
|
|
struct bitmap *bitmap_head = NULL;
|
|
|
|
/** Whether maintenance of the pool states is needed
|
|
*/
|
|
bool bitmap_maintenance = false;
|
|
|
|
/** Whether maintenance of the pool is high priority
|
|
*/
|
|
bool bitmap_maintenance_priority = false;
|
|
|
|
/** Maximum amount of memory for direct images
|
|
*/
|
|
unsigned int bitmap_direct_size;
|
|
|
|
/** Current amount of memory for direct images
|
|
*/
|
|
unsigned int bitmap_direct_used = 0;
|
|
|
|
/** Total size of compressed area
|
|
*/
|
|
unsigned int bitmap_compressed_size;
|
|
|
|
/** Total size of compressed area
|
|
*/
|
|
unsigned int bitmap_compressed_used = 0;
|
|
|
|
/** Total number of suspendable bitmaps
|
|
*/
|
|
unsigned int bitmap_suspendable = 0;
|
|
|
|
/** Total number of suspended bitmaps
|
|
*/
|
|
unsigned int bitmap_suspended = 0;
|
|
|
|
/** Compressed data header
|
|
*/
|
|
struct bitmap_compressed_header {
|
|
int width;
|
|
int height;
|
|
char name[12];
|
|
unsigned int flags;
|
|
unsigned int input_size;
|
|
};
|
|
|
|
char bitmap_unixname[256];
|
|
char bitmap_filename[256];
|
|
|
|
static bool bitmap_initialise(struct bitmap *bitmap);
|
|
static void bitmap_decompress(struct bitmap *bitmap);
|
|
static void bitmap_compress(struct bitmap *bitmap);
|
|
static void bitmap_load_file(struct bitmap *bitmap);
|
|
static void bitmap_save_file(struct bitmap *bitmap);
|
|
static void bitmap_delete_file(struct bitmap *bitmap);
|
|
|
|
|
|
/**
|
|
* Initialise the bitmap memory pool.
|
|
*/
|
|
|
|
void bitmap_initialise_memory(void)
|
|
{
|
|
int available_memory, direct_size, compressed_size;
|
|
int free_slot;
|
|
os_error *error;
|
|
|
|
/* calculate how much memory is currently free
|
|
(Note that the free_slot returned by wimp_slot_size
|
|
includes the next_slot; the value displayed by the
|
|
TaskManager has been adjusted to make it more logical
|
|
for the user).
|
|
*/
|
|
error = xwimp_slot_size(-1, -1, NULL, NULL, &free_slot);
|
|
if (error) {
|
|
LOG(("xwimp_slot_size: 0x%x: %s",
|
|
error->errnum, error->errmess));
|
|
warn_user("WimpError", error->errmess);
|
|
return;
|
|
}
|
|
available_memory = free_slot;
|
|
|
|
/* calculate our memory block sizes */
|
|
if (option_image_memory_direct == -1) {
|
|
/* claim 25% of free memory - min 256KB, max 32768KB */
|
|
direct_size = available_memory / 4;
|
|
if (direct_size < (256 << 10))
|
|
direct_size = (256 << 10);
|
|
if (direct_size > (32768 << 10))
|
|
direct_size = (32768 << 10);
|
|
} else {
|
|
direct_size = (option_image_memory_direct << 10);
|
|
}
|
|
if (option_image_memory_compressed == -1) {
|
|
/* claim 10% of free memory - min 256KB, max 4192KB */
|
|
compressed_size = available_memory / 10;
|
|
if (compressed_size < (256 << 10))
|
|
compressed_size = 0;
|
|
if (compressed_size > (4192 << 10))
|
|
compressed_size = (4192 << 10);
|
|
} else {
|
|
compressed_size = (option_image_memory_compressed << 10);
|
|
}
|
|
|
|
/* set our values. No fixed buffers here, ho hum. */
|
|
bitmap_direct_size = direct_size;
|
|
bitmap_compressed_size = compressed_size;
|
|
bitmap_maintenance = bitmap_maintenance_priority = true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Prepare for the end of a session.
|
|
*/
|
|
|
|
void bitmap_quit(void)
|
|
{
|
|
struct bitmap *bitmap;
|
|
|
|
for (bitmap = bitmap_head; bitmap; bitmap = bitmap->next)
|
|
if ((bitmap->state & BITMAP_PERSISTENT) &&
|
|
((bitmap->state & BITMAP_MODIFIED) ||
|
|
(bitmap->filename[0] == '\0')))
|
|
bitmap_save_file(bitmap);
|
|
}
|
|
|
|
|
|
/**
|
|
* Create a bitmap.
|
|
*
|
|
* \param width width of image in pixels
|
|
* \param height width of image in pixels
|
|
* \param clear whether to clear the image ready for use
|
|
* \return an opaque struct bitmap, or NULL on memory exhaustion
|
|
*/
|
|
|
|
void *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;
|
|
|
|
/* link into our list of bitmaps at the head */
|
|
if (bitmap_head) {
|
|
bitmap->next = bitmap_head;
|
|
bitmap_head->previous = bitmap;
|
|
}
|
|
bitmap_head = bitmap;
|
|
return bitmap;
|
|
}
|
|
|
|
|
|
/**
|
|
* Create a persistent, opaque bitmap from a file reference.
|
|
*
|
|
* \param file the file containing the image data
|
|
* \return an opaque struct bitmap, or NULL on memory exhaustion
|
|
*/
|
|
|
|
struct bitmap *bitmap_create_file(char *file)
|
|
{
|
|
struct bitmap *bitmap;
|
|
char *r;
|
|
fileswitch_object_type obj_type;
|
|
os_error *error;
|
|
|
|
if (file[0] == '\0')
|
|
return NULL;
|
|
|
|
/* check the file exists */
|
|
sprintf(bitmap_unixname, "%s/%s", TEMP_FILENAME_PREFIX, file);
|
|
r = __riscosify(bitmap_unixname, 0, __RISCOSIFY_NO_SUFFIX,
|
|
bitmap_filename, 256, 0);
|
|
if (r == 0) {
|
|
LOG(("__riscosify failed"));
|
|
return NULL;
|
|
}
|
|
error = xosfile_read_stamped_no_path(bitmap_filename,
|
|
&obj_type, 0, 0, 0, 0, 0);
|
|
if ((error) || (obj_type != fileswitch_IS_FILE))
|
|
return NULL;
|
|
|
|
if (!filename_claim(file))
|
|
return NULL;
|
|
bitmap = calloc(1, sizeof(struct bitmap));
|
|
if (!bitmap)
|
|
return NULL;
|
|
bitmap->state = BITMAP_OPAQUE | BITMAP_PERSISTENT | BITMAP_READY;
|
|
strcpy(bitmap->filename, file);
|
|
|
|
/* link in at the head */
|
|
if (bitmap_head) {
|
|
bitmap->next = bitmap_head;
|
|
bitmap_head->previous = bitmap;
|
|
}
|
|
bitmap_head = bitmap;
|
|
return bitmap;
|
|
}
|
|
|
|
|
|
/**
|
|
* Overlay a sprite onto the given bitmap
|
|
*
|
|
* \param bitmap bitmap object
|
|
* \param s 8bpp sprite to be overlayed onto bitmap
|
|
*/
|
|
|
|
void 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*)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;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialise a bitmaps sprite area.
|
|
*
|
|
* \param bitmap the bitmap to initialise
|
|
* \param clear whether to clear the image ready for use
|
|
*/
|
|
|
|
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;
|
|
bitmap->state |= BITMAP_READY;
|
|
bitmap_direct_used += area_size;
|
|
|
|
/* 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;
|
|
|
|
bitmap_maintenance = true;
|
|
bitmap_maintenance_priority |=
|
|
(bitmap_direct_used > bitmap_direct_size * 0.9);
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets whether a bitmap should be plotted opaque
|
|
*
|
|
* \param bitmap a bitmap, as returned by bitmap_create()
|
|
* \param opaque whether the bitmap should be plotted opaque
|
|
*/
|
|
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;
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests whether a bitmap has an opaque alpha channel
|
|
*
|
|
* \param bitmap a bitmap, as returned by bitmap_create()
|
|
* \return whether the bitmap is opaque
|
|
*/
|
|
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 = 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;
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets whether a bitmap should be plotted opaque
|
|
*
|
|
* \param bitmap a bitmap, as returned by bitmap_create()
|
|
*/
|
|
bool bitmap_get_opaque(void *vbitmap)
|
|
{
|
|
struct bitmap *bitmap = (struct bitmap *) vbitmap;
|
|
assert(bitmap);
|
|
return (bitmap->state & BITMAP_OPAQUE);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a pointer to the pixel data in a bitmap.
|
|
*
|
|
* \param bitmap a bitmap, as returned by bitmap_create()
|
|
* \return pointer to the pixel buffer
|
|
*
|
|
* The pixel data is packed as BITMAP_FORMAT, possibly with padding at the end
|
|
* of rows. The width of a row in bytes is given by bitmap_get_rowstride().
|
|
*/
|
|
|
|
unsigned char *bitmap_get_buffer(void *vbitmap)
|
|
{
|
|
struct bitmap *bitmap = (struct bitmap *) vbitmap;
|
|
assert(bitmap);
|
|
|
|
/* move to the head of the list */
|
|
if (bitmap_head != bitmap) {
|
|
if (bitmap->previous)
|
|
bitmap->previous->next = bitmap->next;
|
|
if (bitmap->next)
|
|
bitmap->next->previous = bitmap->previous;
|
|
bitmap->next = bitmap_head;
|
|
bitmap_head->previous = bitmap;
|
|
bitmap->previous = NULL;
|
|
bitmap_head = bitmap;
|
|
}
|
|
|
|
/* dynamically create the buffer */
|
|
if (!(bitmap->state & BITMAP_READY)) {
|
|
if (!bitmap_initialise(bitmap))
|
|
return NULL;
|
|
}
|
|
|
|
/* reset our suspended flag */
|
|
if (bitmap->state & BITMAP_SUSPENDED)
|
|
bitmap->state &= ~BITMAP_SUSPENDED;
|
|
|
|
/* image is already decompressed, no change to image states */
|
|
if (bitmap->sprite_area)
|
|
return ((unsigned char *) (bitmap->sprite_area)) + 16 + 44;
|
|
|
|
/* load and/or decompress the image */
|
|
if (bitmap->filename[0])
|
|
bitmap_load_file(bitmap);
|
|
if (bitmap->compressed)
|
|
bitmap_decompress(bitmap);
|
|
|
|
bitmap_maintenance = true;
|
|
bitmap_maintenance_priority |=
|
|
(bitmap_direct_used > bitmap_direct_size * 0.9);
|
|
|
|
if (bitmap->sprite_area)
|
|
return ((unsigned char *) (bitmap->sprite_area)) + 16 + 44;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find the width of a pixel row in bytes.
|
|
*
|
|
* \param bitmap a bitmap, as returned by bitmap_create()
|
|
* \return width of a pixel row in the bitmap
|
|
*/
|
|
|
|
size_t bitmap_get_rowstride(void *vbitmap)
|
|
{
|
|
struct bitmap *bitmap = (struct bitmap *) vbitmap;
|
|
return bitmap->width * 4;
|
|
}
|
|
|
|
|
|
/**
|
|
* Free a bitmap.
|
|
*
|
|
* \param bitmap a bitmap, as returned by bitmap_create()
|
|
*/
|
|
|
|
void bitmap_destroy(void *vbitmap)
|
|
{
|
|
struct bitmap *bitmap = (struct bitmap *) vbitmap;
|
|
struct bitmap_compressed_header *header;
|
|
unsigned int area_size;
|
|
|
|
assert(bitmap);
|
|
|
|
/* delink from list */
|
|
bitmap_maintenance = true;
|
|
if (bitmap_head == bitmap)
|
|
bitmap_head = bitmap->next;
|
|
if (bitmap->previous)
|
|
bitmap->previous->next = bitmap->next;
|
|
if (bitmap->next)
|
|
bitmap->next->previous = bitmap->previous;
|
|
|
|
/* destroy bitmap */
|
|
if (bitmap->sprite_area) {
|
|
area_size = 16 + 44 + bitmap->width * bitmap->height * 4;
|
|
bitmap_direct_used -= area_size;
|
|
free(bitmap->sprite_area);
|
|
}
|
|
if (bitmap->compressed) {
|
|
header = (void *) bitmap->compressed;
|
|
bitmap_compressed_used -= header->input_size +
|
|
sizeof(struct bitmap_compressed_header);
|
|
free(bitmap->compressed);
|
|
}
|
|
if (bitmap->filename[0])
|
|
bitmap_delete_file(bitmap);
|
|
free(bitmap);
|
|
}
|
|
|
|
|
|
/**
|
|
* Save a bitmap in the platform's native format.
|
|
*
|
|
* \param bitmap a bitmap, as returned by bitmap_create()
|
|
* \param path pathname for file
|
|
* \param flags modify the behaviour of the save
|
|
* \return true on success, false on error and error reported
|
|
*/
|
|
|
|
bool bitmap_save(void *vbitmap, const char *path, unsigned flags)
|
|
{
|
|
struct bitmap *bitmap = (struct bitmap *) vbitmap;
|
|
os_error *error;
|
|
|
|
if (!bitmap->sprite_area)
|
|
bitmap_get_buffer(bitmap);
|
|
if (!bitmap->sprite_area)
|
|
return false;
|
|
|
|
if (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));
|
|
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) {
|
|
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);
|
|
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);
|
|
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);
|
|
warn_user("SaveError", error->errmess);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
error = xosfind_closew(fw);
|
|
if (error) {
|
|
LOG(("xosfind_closew: 0x%x: %s",
|
|
error->errnum, error->errmess));
|
|
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));
|
|
warn_user("SaveError", error->errmess);
|
|
}
|
|
|
|
free(chunk_buf);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* The bitmap image has changed, so flush any persistent cache.
|
|
*
|
|
* \param bitmap a bitmap, as returned by bitmap_create()
|
|
*/
|
|
void bitmap_modified(void *vbitmap) {
|
|
struct bitmap *bitmap = (struct bitmap *) vbitmap;
|
|
bitmap->state |= BITMAP_MODIFIED;
|
|
}
|
|
|
|
|
|
/**
|
|
* The bitmap image can be suspended.
|
|
*
|
|
* \param bitmap a bitmap, as returned by bitmap_create()
|
|
* \param private_word a private word to be returned later
|
|
* \param invalidate the function to be called upon suspension
|
|
*/
|
|
void bitmap_set_suspendable(void *vbitmap, void *private_word,
|
|
void (*invalidate)(void *bitmap, void *private_word)) {
|
|
struct bitmap *bitmap = (struct bitmap *) vbitmap;
|
|
|
|
bitmap->private_word = private_word;
|
|
bitmap->invalidate = invalidate;
|
|
bitmap_suspendable++;
|
|
}
|
|
|
|
|
|
/**
|
|
* Performs routine maintenance.
|
|
*/
|
|
void bitmap_maintain(void)
|
|
{
|
|
unsigned int memory = 0;
|
|
unsigned int compressed_memory = 0;
|
|
unsigned int suspended = 0;
|
|
struct bitmap *bitmap = bitmap_head;
|
|
struct bitmap_compressed_header *header;
|
|
unsigned int maintain_direct_size;
|
|
|
|
LOG(("Performing maintenance."));
|
|
|
|
/* under heavy loads allow an extra 30% to work with */
|
|
maintain_direct_size = bitmap_direct_size;
|
|
if (!bitmap_maintenance_priority)
|
|
maintain_direct_size = maintain_direct_size * 0.7;
|
|
|
|
if ((!bitmap) || ((bitmap_direct_used < maintain_direct_size) &&
|
|
(bitmap_compressed_used < bitmap_compressed_size))) {
|
|
bitmap_maintenance = bitmap_maintenance_priority;
|
|
bitmap_maintenance_priority = false;
|
|
return;
|
|
}
|
|
|
|
/* we don't change the first bitmap_MEMORY entries as they
|
|
* will automatically be loaded/decompressed from whatever state
|
|
* they are in when neeeded. */
|
|
for (; bitmap && (memory < maintain_direct_size);
|
|
bitmap = bitmap->next) {
|
|
if (bitmap->sprite_area)
|
|
memory += bitmap->width * bitmap->height * 4;
|
|
else if ((bitmap->compressed) &&
|
|
(!bitmap_maintenance_priority)) {
|
|
header = (void *) bitmap->compressed;
|
|
compressed_memory += header->input_size +
|
|
sizeof(struct bitmap_compressed_header);
|
|
} else if (bitmap->state & BITMAP_SUSPENDED)
|
|
suspended++;
|
|
}
|
|
|
|
if (!bitmap) {
|
|
bitmap_maintenance = bitmap_maintenance_priority;
|
|
bitmap_maintenance_priority = false;
|
|
return;
|
|
}
|
|
|
|
/* the fastest and easiest way to release memory is by suspending
|
|
* images. as such, we try to do this first for as many images as
|
|
* possible, potentially freeing up large amounts of memory */
|
|
if (suspended <= (bitmap_suspendable - bitmap_suspended)) {
|
|
for (; bitmap; bitmap = bitmap->next) {
|
|
if (bitmap->invalidate) {
|
|
bitmap->invalidate(bitmap, bitmap->private_word);
|
|
free(bitmap->sprite_area);
|
|
bitmap->sprite_area = NULL;
|
|
bitmap->state |= BITMAP_SUSPENDED;
|
|
bitmap->state &= ~BITMAP_READY;
|
|
bitmap_direct_used -= 16 + 44 +
|
|
bitmap->width * bitmap->height * 4;
|
|
bitmap_suspended++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* under heavy loads, we ignore compression */
|
|
if (!bitmap_maintenance_priority) {
|
|
/* for the next section, up until bitmap_COMPRESSED we
|
|
* forcibly compress the data if it's currently held directly
|
|
* in memory */
|
|
for (; bitmap && (compressed_memory < bitmap_compressed_size);
|
|
bitmap = bitmap->next) {
|
|
if (bitmap->sprite_area) {
|
|
if ((bitmap->width * bitmap->height) <=
|
|
(512 * 512))
|
|
bitmap_compress(bitmap);
|
|
else
|
|
bitmap_save_file(bitmap);
|
|
return;
|
|
}
|
|
if (bitmap->compressed) {
|
|
header = (void *) bitmap->compressed;
|
|
compressed_memory += header->input_size +
|
|
sizeof(struct bitmap_compressed_header);
|
|
}
|
|
}
|
|
if (!bitmap) {
|
|
bitmap_maintenance = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* for the remaining entries we dump to disk */
|
|
for (; bitmap; bitmap = bitmap->next) {
|
|
if ((bitmap->sprite_area) || (bitmap->compressed)) {
|
|
if (bitmap_maintenance_priority) {
|
|
if (bitmap->sprite_area)
|
|
bitmap_save_file(bitmap);
|
|
|
|
} else {
|
|
bitmap_save_file(bitmap);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
bitmap_maintenance = bitmap_maintenance_priority;
|
|
bitmap_maintenance_priority = false;
|
|
}
|
|
|
|
|
|
void bitmap_decompress(struct bitmap *bitmap)
|
|
{
|
|
unsigned int area_size;
|
|
_kernel_oserror *error;
|
|
int output_size;
|
|
struct bitmap_compressed_header *header;
|
|
|
|
assert(bitmap->compressed);
|
|
|
|
/* ensure the width/height is correct */
|
|
header = (void *)bitmap->compressed;
|
|
if ((header->width != bitmap->width) ||
|
|
(header->height != bitmap->height)) {
|
|
LOG(("Warning: Mismatch between bitmap and compressed sizes"));
|
|
return;
|
|
}
|
|
|
|
/* create the image memory/header to decompress to */
|
|
if (!bitmap_initialise(bitmap))
|
|
return;
|
|
|
|
/* decompress the data */
|
|
output_size = bitmap->width * bitmap->height * 4 +
|
|
sizeof(struct osspriteop_header);
|
|
error = _swix(Tinct_Decompress, _IN(0) | _IN(2) | _IN(3) | _IN(7),
|
|
bitmap->compressed,
|
|
(char *)(bitmap->sprite_area + 1),
|
|
output_size,
|
|
0);
|
|
if (error) {
|
|
LOG(("Decompression error"));
|
|
free(bitmap->sprite_area);
|
|
bitmap->sprite_area = NULL;
|
|
} else {
|
|
LOG(("Decompressed"));
|
|
area_size = header->input_size +
|
|
sizeof(struct bitmap_compressed_header);
|
|
bitmap_compressed_used -= area_size;
|
|
free(bitmap->compressed);
|
|
bitmap->compressed = NULL;
|
|
area_size = 16 + 44 + bitmap->width * bitmap->height * 4;
|
|
bitmap_direct_used += area_size;
|
|
}
|
|
}
|
|
|
|
|
|
void bitmap_compress(struct bitmap *bitmap)
|
|
{
|
|
unsigned int area_size;
|
|
_kernel_oserror *error;
|
|
char *output;
|
|
unsigned int output_size, new_size;
|
|
unsigned int flags = 0;
|
|
float calc;
|
|
|
|
/* get the maximum output size (33/32 * size) */
|
|
output_size = ((bitmap->width * bitmap->height * 4 * 33) >> 5) +
|
|
sizeof(struct bitmap_compressed_header);
|
|
output = malloc(output_size);
|
|
if (!output)
|
|
return;
|
|
|
|
/* compress the data */
|
|
if (bitmap->state & BITMAP_OPAQUE)
|
|
flags |= tinct_OPAQUE_IMAGE;
|
|
error = _swix(Tinct_Compress, _IN(0) | _IN(2) | _IN(7) | _OUT(0),
|
|
(char *)(bitmap->sprite_area + 1),
|
|
output,
|
|
flags,
|
|
&new_size);
|
|
if (error) {
|
|
LOG(("Compression error"));
|
|
free(output);
|
|
} else {
|
|
bitmap->compressed = realloc(output, new_size);
|
|
if (!bitmap->compressed) {
|
|
free(output);
|
|
} else {
|
|
bitmap_compressed_used += new_size;
|
|
if (bitmap->sprite_area) {
|
|
area_size = 16 + 44 + bitmap->width *
|
|
bitmap->height * 4;
|
|
bitmap_direct_used -= area_size;
|
|
free(bitmap->sprite_area);
|
|
}
|
|
bitmap->sprite_area = NULL;
|
|
calc = (100 / (float)output_size) * new_size;
|
|
LOG(("Compression: %i->%i, %.3f%%",
|
|
output_size, new_size, calc));
|
|
}
|
|
}
|
|
}
|
|
|
|
void bitmap_load_file(struct bitmap *bitmap)
|
|
{
|
|
int len;
|
|
fileswitch_object_type obj_type;
|
|
os_error *error;
|
|
char *r;
|
|
struct bitmap_compressed_header *bitmap_compressed;
|
|
osspriteop_header *bitmap_direct;
|
|
int *data;
|
|
|
|
assert(bitmap->filename);
|
|
|
|
sprintf(bitmap_unixname, "%s/%s", TEMP_FILENAME_PREFIX,
|
|
bitmap->filename);
|
|
r = __riscosify(bitmap_unixname, 0, __RISCOSIFY_NO_SUFFIX,
|
|
bitmap_filename, 256, 0);
|
|
if (r == 0) {
|
|
LOG(("__riscosify failed"));
|
|
return;
|
|
}
|
|
error = xosfile_read_stamped_no_path(bitmap_filename,
|
|
&obj_type, 0, 0, &len, 0, 0);
|
|
if ((error) || (obj_type != fileswitch_IS_FILE))
|
|
return;
|
|
|
|
bitmap->compressed = malloc(len);
|
|
if (!bitmap->compressed)
|
|
return;
|
|
|
|
error = xosfile_load_stamped_no_path(bitmap_filename,
|
|
(byte *) bitmap->compressed, 0, 0, 0, 0, 0);
|
|
if (error) {
|
|
free(bitmap->compressed);
|
|
bitmap->compressed = NULL;
|
|
return;
|
|
}
|
|
|
|
data = (void *) bitmap->compressed;
|
|
|
|
LOG(("Loaded file from disk"));
|
|
/* Sanity check the file we've just loaded:
|
|
* If it's an uncompressed buffer, then it's a raw sprite area,
|
|
* including the total size word at the start. Therefore, we check
|
|
* that:
|
|
* a) The declared total area size == file length
|
|
* b) The offset to the first free word == file length
|
|
* c) There is only 1 sprite in the area
|
|
* d) The name of the sprite in the area is "bitmap"
|
|
*
|
|
* If it's a compressed buffer, then we check that:
|
|
* a) The declared input size + header size == file length
|
|
* b) The name of the buffer is "bitmap"
|
|
*
|
|
* If it's neither of these, we fail.
|
|
*/
|
|
if (*data == len && *(data + 3) == len && *(data + 1) == 1 &&
|
|
strncmp(bitmap->compressed + 20, "bitmap", 6) == 0) {
|
|
bitmap->sprite_area = (void *) bitmap->compressed;
|
|
bitmap->compressed = NULL;
|
|
bitmap_direct = (osspriteop_header *)(bitmap->sprite_area + 1);
|
|
bitmap->width = bitmap_direct->width + 1;
|
|
bitmap->height = bitmap_direct->height + 1;
|
|
bitmap_direct_used += 16 + 44 +
|
|
bitmap->width * bitmap->height * 4;
|
|
} else if ((int) (*(data + 6) +
|
|
sizeof(struct bitmap_compressed_header)) == len &&
|
|
strncmp(bitmap->compressed + 8, "bitmap", 6) == 0) {
|
|
bitmap_compressed = (void *) bitmap->compressed;
|
|
bitmap_compressed_used -= bitmap_compressed->input_size +
|
|
sizeof(struct bitmap_compressed_header);
|
|
bitmap->width = bitmap_compressed->width;
|
|
bitmap->height = bitmap_compressed->height;
|
|
} else {
|
|
free(bitmap->compressed);
|
|
bitmap->compressed = NULL;
|
|
return;
|
|
}
|
|
if (bitmap->state & BITMAP_MODIFIED)
|
|
bitmap_delete_file(bitmap);
|
|
}
|
|
|
|
|
|
void bitmap_save_file(struct bitmap *bitmap)
|
|
{
|
|
unsigned int area_size;
|
|
const char *filename;
|
|
char *r;
|
|
os_error *error;
|
|
struct bitmap_compressed_header *header;
|
|
|
|
assert(bitmap);
|
|
|
|
if (!bitmap->compressed && !bitmap->sprite_area) {
|
|
LOG(("bitmap has no data"));
|
|
return;
|
|
}
|
|
|
|
/* unmodified bitmaps will still have their file available */
|
|
if ((!(bitmap->state & BITMAP_MODIFIED)) && bitmap->filename[0]) {
|
|
if (bitmap->sprite_area)
|
|
free(bitmap->sprite_area);
|
|
bitmap->sprite_area = NULL;
|
|
if (bitmap->compressed)
|
|
free(bitmap->compressed);
|
|
bitmap->compressed = NULL;
|
|
return;
|
|
}
|
|
|
|
/* dump the data (compressed or otherwise) to disk */
|
|
filename = filename_request();
|
|
if (!filename) {
|
|
LOG(("filename_request failed"));
|
|
return;
|
|
}
|
|
|
|
strcpy(bitmap->filename, filename);
|
|
sprintf(bitmap_unixname, "%s/%s", TEMP_FILENAME_PREFIX,
|
|
bitmap->filename);
|
|
r = __riscosify(bitmap_unixname, 0, __RISCOSIFY_NO_SUFFIX,
|
|
bitmap_filename, 256, 0);
|
|
if (r == 0) {
|
|
LOG(("__riscosify failed"));
|
|
return;
|
|
}
|
|
if (bitmap->compressed) {
|
|
header = (void *) bitmap->compressed;
|
|
area_size = header->input_size +
|
|
sizeof(struct bitmap_compressed_header);
|
|
error = xosfile_save_stamped(bitmap_filename, 0xffd,
|
|
(byte *) bitmap->compressed,
|
|
(byte *) bitmap->compressed + area_size);
|
|
} else {
|
|
area_size = bitmap->width * bitmap->height * 4 +
|
|
sizeof(osspriteop_header) +
|
|
sizeof(osspriteop_area);
|
|
error = xosfile_save_stamped(bitmap_filename, 0xffd,
|
|
(byte *) bitmap->sprite_area,
|
|
((byte *) bitmap->sprite_area) + area_size);
|
|
}
|
|
|
|
if (error) {
|
|
LOG(("xosfile_save_stamped: 0x%x: %s",
|
|
error->errnum, error->errmess));
|
|
bitmap->filename[0] = 0;
|
|
} else {
|
|
if (bitmap->sprite_area) {
|
|
bitmap_direct_used -= area_size;
|
|
free(bitmap->sprite_area);
|
|
}
|
|
bitmap->sprite_area = NULL;
|
|
if (bitmap->compressed) {
|
|
bitmap_compressed_used -= area_size;
|
|
free(bitmap->compressed);
|
|
}
|
|
bitmap->compressed = NULL;
|
|
bitmap->state &= ~BITMAP_MODIFIED;
|
|
LOG(("Saved file to disk"));
|
|
}
|
|
}
|
|
|
|
|
|
void bitmap_delete_file(struct bitmap *bitmap)
|
|
{
|
|
assert(bitmap->filename[0]);
|
|
filename_release(bitmap->filename);
|
|
bitmap->filename[0] = 0;
|
|
}
|
|
|
|
|
|
int bitmap_get_width(void *vbitmap)
|
|
{
|
|
struct bitmap *bitmap = (struct bitmap *) vbitmap;
|
|
return bitmap->width;
|
|
}
|
|
|
|
|
|
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
|
|
*/
|
|
|
|
size_t bitmap_get_bpp(void *vbitmap)
|
|
{
|
|
struct bitmap *bitmap = (struct bitmap *)vbitmap;
|
|
assert(bitmap);
|
|
return 4;
|
|
}
|
|
|