diff --git a/src/spritecache.cpp b/src/spritecache.cpp --- a/src/spritecache.cpp +++ b/src/spritecache.cpp @@ -13,11 +13,14 @@ #include "fileio_func.h" #include "spriteloader/grf.hpp" #include "gfx_func.h" +#include "zoom_func.h" +#include "settings_type.h" #ifdef WITH_PNG #include "spriteloader/png.hpp" #endif /* WITH_PNG */ #include "blitter/factory.hpp" #include "core/math_func.hpp" +#include "core/mem_func.hpp" #include "table/sprites.h" #include "table/palette_convert.h" @@ -156,6 +159,177 @@ uint GetMaxSpriteID() return _spritecache_items; } +static bool ResizeSpriteIn(SpriteLoader::Sprite *sprite, ZoomLevel src, ZoomLevel tgt) +{ + uint8 scaled_1 = UnScaleByZoom(1, (ZoomLevel)(tgt - src)); + + /* Check for possible memory overflow. */ + if (sprite[src].width * scaled_1 > UINT16_MAX || sprite[src].height * scaled_1 > UINT16_MAX) return false; + + sprite[tgt].width = sprite[src].width * scaled_1; + sprite[tgt].height = sprite[src].height * scaled_1; + sprite[tgt].x_offs = sprite[src].x_offs * scaled_1; + sprite[tgt].y_offs = sprite[src].y_offs * scaled_1; + + sprite[tgt].AllocateData(tgt, sprite[tgt].width * sprite[tgt].height); + + SpriteLoader::CommonPixel *dst = sprite[tgt].data; + for (int y = 0; y < sprite[tgt].height; y++) { + const SpriteLoader::CommonPixel *src_ln = &sprite[src].data[y / scaled_1 * sprite[src].width]; + for (int x = 0; x < sprite[tgt].width; x++) { + *dst = src_ln[x / scaled_1]; + dst++; + } + } + + return true; +} + +static void ResizeSpriteOut(SpriteLoader::Sprite *sprite, ZoomLevel zoom) +{ + /* Algorithm based on 32bpp_Optimized::ResizeSprite() */ + sprite[zoom].width = UnScaleByZoom(sprite[ZOOM_LVL_NORMAL].width, zoom); + sprite[zoom].height = UnScaleByZoom(sprite[ZOOM_LVL_NORMAL].height, zoom); + sprite[zoom].x_offs = UnScaleByZoom(sprite[ZOOM_LVL_NORMAL].x_offs, zoom); + sprite[zoom].y_offs = UnScaleByZoom(sprite[ZOOM_LVL_NORMAL].y_offs, zoom); + + sprite[zoom].AllocateData(zoom, sprite[zoom].height * sprite[zoom].width); + + SpriteLoader::CommonPixel *dst = sprite[zoom].data; + const SpriteLoader::CommonPixel *src = sprite[zoom - 1].data; + const SpriteLoader::CommonPixel *src_end = src + sprite[zoom - 1].height * sprite[zoom - 1].width; + + for (uint y = 0; y < sprite[zoom].height; y++) { + if (src >= src_end) src = src_end - sprite[zoom - 1].width; + + const SpriteLoader::CommonPixel *src_ln = src + sprite[zoom - 1].width * 2; + for (uint x = 0; x < sprite[zoom].width; x++) { + if (src >= src_ln) src = src_ln - 1; + if ((src + 1)->a != 0) { *dst = *(src + 1); } + else { *dst = *src; } + dst++; + src += 2; + } + + src = src_ln; + } +} + +static bool PadSingleSprite(SpriteLoader::Sprite *sprite, ZoomLevel zoom, uint pad_left, uint pad_top, uint pad_right, uint pad_bottom) +{ + uint width = sprite->width + pad_left + pad_right; + uint height = sprite->height + pad_top + pad_bottom; + + if (width > UINT16_MAX || height > UINT16_MAX) return false; + + /* Copy source data and reallocate sprite memory. */ + SpriteLoader::CommonPixel *src_data = MallocT(sprite->width * sprite->height); + MemCpyT(src_data, sprite->data, sprite->width * sprite->height); + sprite->AllocateData(zoom, width * height); + + /* Copy with padding to destination. */ + SpriteLoader::CommonPixel *src = src_data; + SpriteLoader::CommonPixel *data = sprite->data; + for (uint y = 0; y < height; y++) { + if (y < pad_top || pad_bottom + y >= height) { + /* Top/bottom padding. */ + MemSetT(data, 0, width); + data += width; + } else { + if (pad_left > 0) { + /* Pad left. */ + MemSetT(data, 0, pad_left); + data += pad_left; + } + + /* Copy pixels. */ + MemCpyT(data, src, sprite->width); + src += sprite->width; + data += sprite->width; + + if (pad_right > 0) { + /* Pad right. */ + MemSetT(data, 0, pad_right); + data += pad_right; + } + } + } + free(src_data); + + /* Update sprite size. */ + sprite->width = width; + sprite->height = height; + sprite->x_offs -= pad_left; + sprite->y_offs -= pad_top; + + return true; +} + +static bool PadSprites(SpriteLoader::Sprite *sprite, uint8 sprite_avail) +{ + int left = sprite[ZOOM_LVL_NORMAL].x_offs; + int top = sprite[ZOOM_LVL_NORMAL].y_offs; + int right = sprite[ZOOM_LVL_NORMAL].x_offs + sprite[ZOOM_LVL_NORMAL].width; + int bottom = sprite[ZOOM_LVL_NORMAL].y_offs + sprite[ZOOM_LVL_NORMAL].height; + + /* Find combined bounds of all zoom levels.*/ + for (ZoomLevel zoom = ZOOM_LVL_OUT_2X; zoom != ZOOM_LVL_END; zoom++) { + if (HasBit(sprite_avail, zoom)) { + uint8 scaled_1 = ScaleByZoom(1, zoom); + + left = min(left, sprite[zoom].x_offs * scaled_1); + top = min(top, sprite[zoom].y_offs * scaled_1); + right = max(right, (sprite[zoom].x_offs + sprite[zoom].width - 1) * scaled_1); + bottom = max(bottom, (sprite[zoom].y_offs + sprite[zoom].height - 1) * scaled_1); + } + } + + /* Pad too small sprites. */ + SetBit(sprite_avail, ZOOM_LVL_NORMAL); + for (ZoomLevel zoom = ZOOM_LVL_BEGIN; zoom != ZOOM_LVL_END; zoom++) { + if (HasBit(sprite_avail, zoom)) { + int pad_left = sprite[zoom].x_offs - UnScaleByZoom(left, zoom); + int pad_top = sprite[zoom].y_offs - UnScaleByZoom(top, zoom); + int pad_right = UnScaleByZoom(right, zoom) - (sprite[zoom].x_offs + sprite[zoom].width); + int pad_bottom = UnScaleByZoom(bottom, zoom) - (sprite[zoom].y_offs + sprite[zoom].height); + + if (pad_left != 0 || pad_right != 0 || pad_top != 0 || pad_bottom != 0) { + if (!PadSingleSprite(&sprite[zoom], zoom, pad_left, pad_top, pad_right, pad_bottom)) return false; + } + } + } + + return true; +} + +static bool ResizeSprites(SpriteLoader::Sprite *sprite, uint8 sprite_avail, uint32 file_slot, uint32 file_pos) +{ + /* Create a fully zoomed image if it does not exist */ + ZoomLevel first_avail = static_cast(FIND_FIRST_BIT(sprite_avail)); + if (first_avail != ZOOM_LVL_NORMAL) { + if (!ResizeSpriteIn(sprite, first_avail, ZOOM_LVL_NORMAL)) return false; + } + + /* Pad sprites to make sizes match. */ + if (!PadSprites(sprite, sprite_avail)) return false; + + /* Create other missing zoom levels */ + for (ZoomLevel zoom = ZOOM_LVL_OUT_2X; zoom != ZOOM_LVL_END; zoom++) { + if (HasBit(sprite_avail, zoom)) { + /* Check that size and offsets match the fully zoomed image. */ + assert(sprite[zoom].width == UnScaleByZoom(sprite[ZOOM_LVL_NORMAL].width, zoom)); + assert(sprite[zoom].height == UnScaleByZoom(sprite[ZOOM_LVL_NORMAL].height, zoom)); + assert(sprite[zoom].x_offs == UnScaleByZoom(sprite[ZOOM_LVL_NORMAL].x_offs, zoom)); + assert(sprite[zoom].y_offs == UnScaleByZoom(sprite[ZOOM_LVL_NORMAL].y_offs, zoom)); + } + + /* Zoom level is not available, or unusable, so create it */ + if (!HasBit(sprite_avail, zoom)) ResizeSpriteOut(sprite, zoom); + } + + return true; +} + /** * Load a recolour sprite into memory. * @param file_slot GRF we're reading from. @@ -208,15 +382,21 @@ static void *ReadSprite(const SpriteCach DEBUG(sprite, 9, "Load sprite %d", id); + SpriteLoader::Sprite sprite[ZOOM_LVL_COUNT]; + uint8 sprite_avail = 0; + sprite[ZOOM_LVL_NORMAL].type = sprite_type; + if (sprite_type == ST_NORMAL && BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth() == 32) { #ifdef WITH_PNG /* Try loading 32bpp graphics in case we are 32bpp output */ SpriteLoaderPNG sprite_loader; - SpriteLoader::Sprite sprite; - sprite.type = sprite_type; + + sprite_avail = sprite_loader.LoadSprite(sprite, file_slot, sc->id, sprite_type); - if (sprite_loader.LoadSprite(&sprite, file_slot, sc->id, sprite_type)) { - return BlitterFactoryBase::GetCurrentBlitter()->Encode(&sprite, allocator); + if (sprite_avail != 0) { + if (ResizeSprites(sprite, sprite_avail, file_slot, sc->id)) { + return BlitterFactoryBase::GetCurrentBlitter()->Encode(sprite, allocator); + } } /* If the PNG couldn't be loaded, fall back to 8bpp grfs */ #else @@ -229,10 +409,9 @@ static void *ReadSprite(const SpriteCach } SpriteLoaderGrf sprite_loader(sc->container_ver); - SpriteLoader::Sprite sprite; - sprite.type = sprite_type; + sprite_avail = sprite_loader.LoadSprite(sprite, file_slot, file_pos, sprite_type); - if (!sprite_loader.LoadSprite(&sprite, file_slot, file_pos, sprite_type)) { + if (sprite_avail == 0) { if (sprite_type == ST_MAPGEN) return NULL; if (id == SPR_IMG_QUERY) usererror("Okay... something went horribly wrong. I couldn't load the fallback sprite. What should I do?"); return (void*)GetRawSprite(SPR_IMG_QUERY, ST_NORMAL, allocator); @@ -248,15 +427,15 @@ static void *ReadSprite(const SpriteCach * Ugly: yes. Other solution: no. Blame the original author or * something ;) The image should really have been a data-stream * (so type = 0xFF basicly). */ - uint num = sprite.width * sprite.height; + uint num = sprite[ZOOM_LVL_NORMAL].width * sprite[ZOOM_LVL_NORMAL].height; Sprite *s = (Sprite *)allocator(sizeof(*s) + num); - s->width = sprite.width; - s->height = sprite.height; - s->x_offs = sprite.x_offs; - s->y_offs = sprite.y_offs; + s->width = sprite[ZOOM_LVL_NORMAL].width; + s->height = sprite[ZOOM_LVL_NORMAL].height; + s->x_offs = sprite[ZOOM_LVL_NORMAL].x_offs; + s->y_offs = sprite[ZOOM_LVL_NORMAL].y_offs; - SpriteLoader::CommonPixel *src = sprite.data; + SpriteLoader::CommonPixel *src = sprite[ZOOM_LVL_NORMAL].data; byte *dest = s->data; while (num-- > 0) { *dest++ = src->m; @@ -266,7 +445,13 @@ static void *ReadSprite(const SpriteCach return s; } - return BlitterFactoryBase::GetCurrentBlitter()->Encode(&sprite, allocator); + if (sprite_type == ST_NORMAL) { + if (!ResizeSprites(sprite, sprite_avail, file_slot, sc->id)) { + if (id == SPR_IMG_QUERY) usererror("Okay... something went horribly wrong. I couldn't resize the fallback sprite. What should I do?"); + return (void*)GetRawSprite(SPR_IMG_QUERY, ST_NORMAL, allocator); + } + } + return BlitterFactoryBase::GetCurrentBlitter()->Encode(sprite, allocator); } @@ -704,4 +889,4 @@ void GfxClearSpriteCache() } } -/* static */ ReusableBuffer SpriteLoader::Sprite::buffer; +/* static */ ReusableBuffer SpriteLoader::Sprite::buffer[ZOOM_LVL_COUNT];