diff options
Diffstat (limited to 'src/libs/graphics/sdl/canvas.c')
-rw-r--r-- | src/libs/graphics/sdl/canvas.c | 2176 |
1 files changed, 2176 insertions, 0 deletions
diff --git a/src/libs/graphics/sdl/canvas.c b/src/libs/graphics/sdl/canvas.c new file mode 100644 index 0000000..ad7024d --- /dev/null +++ b/src/libs/graphics/sdl/canvas.c @@ -0,0 +1,2176 @@ +/* + * This program 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "port.h" +#include <string.h> + // for memcpy() + +#include SDL_INCLUDE(SDL.h) +#include "sdl_common.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/graphics/cmap.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "primitives.h" +#include "palette.h" +#include "sdluio.h" +#include "rotozoom.h" +#include "options.h" +#include "types.h" + +typedef SDL_Surface *NativeCanvas; + +// BYTE x BYTE weight (mult >> 8) table +static Uint8 btable[256][256]; + +void +TFB_DrawCanvas_Initialize (void) +{ + int i, j; + for (i = 0; i < 256; ++i) + for (j = 0; j < 256; ++j) + btable[j][i] = (j * i + 0x80) >> 8; + // need error correction here +} + +const char * +TFB_DrawCanvas_GetError (void) +{ + const char *err = SDL_GetError (); + // TODO: Should we call SDL_ClearError() here so that it is not + // returned again later? + return err; +} + +static void +checkPrimitiveMode (SDL_Surface *surf, Color *color, DrawMode *mode) +{ + const SDL_PixelFormat *fmt = surf->format; + // Special case: We support DRAW_ALPHA mode to non-alpha surfaces + // for primitives via Color.a + if (mode->kind == DRAW_REPLACE && fmt->Amask == 0 && color->a != 0xff) + { + mode->kind = DRAW_ALPHA; + mode->factor = color->a; + color->a = 0xff; + } +} + +void +TFB_DrawCanvas_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode mode, TFB_Canvas target) +{ + SDL_Surface *dst = target; + SDL_PixelFormat *fmt = dst->format; + Uint32 sdlColor; + RenderPixelFn plotFn; + + checkPrimitiveMode (dst, &color, &mode); + sdlColor = SDL_MapRGBA (fmt, color.r, color.g, color.b, color.a); + + plotFn = renderpixel_for (target, mode.kind); + if (!plotFn) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_Line " + "unsupported draw mode (%d)", (int)mode.kind); + return; + } + + SDL_LockSurface (dst); + line_prim (x1, y1, x2, y2, sdlColor, plotFn, mode.factor, dst); + SDL_UnlockSurface (dst); +} + +void +TFB_DrawCanvas_Rect (RECT *rect, Color color, DrawMode mode, TFB_Canvas target) +{ + SDL_Surface *dst = target; + SDL_PixelFormat *fmt = dst->format; + Uint32 sdlColor; + SDL_Rect sr; + sr.x = rect->corner.x; + sr.y = rect->corner.y; + sr.w = rect->extent.width; + sr.h = rect->extent.height; + + checkPrimitiveMode (dst, &color, &mode); + sdlColor = SDL_MapRGBA (fmt, color.r, color.g, color.b, color.a); + + if (mode.kind == DRAW_REPLACE) + { // Standard SDL fillrect rendering + Uint32 colorkey; + if (fmt->Amask && (TFB_GetColorKey (dst, &colorkey) == 0)) + { // special case -- alpha surface with colorkey + // colorkey rects are transparent + if ((sdlColor & ~fmt->Amask) == (colorkey & ~fmt->Amask)) + sdlColor &= ~fmt->Amask; // make transparent + } + SDL_FillRect (dst, &sr, sdlColor); + } + else + { // Custom fillrect rendering + RenderPixelFn plotFn = renderpixel_for (target, mode.kind); + if (!plotFn) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_Rect " + "unsupported draw mode (%d)", (int)mode.kind); + return; + } + + SDL_LockSurface (dst); + fillrect_prim (sr, sdlColor, plotFn, mode.factor, dst); + SDL_UnlockSurface (dst); + } +} + +static void +TFB_DrawCanvas_Blit (SDL_Surface *src, SDL_Rect *src_r, + SDL_Surface *dst, SDL_Rect *dst_r, DrawMode mode) +{ + SDL_PixelFormat *srcfmt = src->format; + + if (mode.kind == DRAW_REPLACE) + { // Standard SDL simple blit + SDL_BlitSurface (src, src_r, dst, dst_r); + } + else if (mode.kind == DRAW_ALPHA && srcfmt->Amask == 0) + { // Standard SDL surface-alpha blit + // Note that surface alpha and per-pixel alpha cannot work + // at the same time, which is why the Amask test + int hasAlpha = TFB_HasSurfaceAlphaMod (src); + assert (!hasAlpha); + // Set surface alpha temporarily + TFB_SetSurfaceAlphaMod (src, mode.factor); + SDL_BlitSurface (src, src_r, dst, dst_r); + TFB_DisableSurfaceAlphaMod (src); + } + else + { // Custom blit + SDL_Rect loc_src_r, loc_dst_r; + RenderPixelFn plotFn = renderpixel_for (dst, mode.kind); + if (!plotFn) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_Blit " + "unsupported draw mode (%d)", (int)mode.kind); + return; + } + + if (!src_r) + { // blit whole image; generate rect + loc_src_r.x = 0; + loc_src_r.y = 0; + loc_src_r.w = src->w; + loc_src_r.h = src->h; + src_r = &loc_src_r; + } + + if (!dst_r) + { // blit to 0,0; generate rect + loc_dst_r.x = 0; + loc_dst_r.y = 0; + loc_dst_r.w = dst->w; + loc_dst_r.h = dst->h; + dst_r = &loc_dst_r; + } + + SDL_LockSurface (dst); + blt_prim (src, *src_r, plotFn, mode.factor, dst, *dst_r); + SDL_UnlockSurface (dst); + } +} + +// XXX: If a colormap is passed in, it has to have been acquired via +// TFB_GetColorMap(). We release the colormap at the end. +void +TFB_DrawCanvas_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *cmap, DrawMode mode, TFB_Canvas target) +{ + SDL_Rect srcRect, targetRect, *pSrcRect; + SDL_Surface *surf; + SDL_Palette *NormalPal; + + if (img == 0) + { + log_add (log_Warning, + "ERROR: TFB_DrawCanvas_Image passed null image ptr"); + return; + } + + LockMutex (img->mutex); + + NormalPal = ((SDL_Surface *)img->NormalImg)->format->palette; + // only set the new palette if it changed + if (NormalPal && cmap && img->colormap_version != cmap->version) + TFB_SetColors (img->NormalImg, cmap->palette->colors, 0, 256); + + if (scale != 0 && scale != GSCALE_IDENTITY) + { + if (scaleMode == TFB_SCALE_TRILINEAR && img->MipmapImg) + { + // only set the new palette if it changed + if (TFB_DrawCanvas_IsPaletted (img->MipmapImg) + && cmap && img->colormap_version != cmap->version) + TFB_SetColors (img->MipmapImg, cmap->palette->colors, 0, 256); + } + else if (scaleMode == TFB_SCALE_TRILINEAR && !img->MipmapImg) + { // Do bilinear scaling instead when mipmap is unavailable + scaleMode = TFB_SCALE_BILINEAR; + } + + TFB_DrawImage_FixScaling (img, scale, scaleMode); + surf = img->ScaledImg; + if (TFB_DrawCanvas_IsPaletted (surf)) + { + // We may only get a paletted scaled image if the source is + // paletted. Currently, all scaling targets are truecolor. + assert (NormalPal && NormalPal->colors); + TFB_SetColors (surf, NormalPal->colors, 0, NormalPal->ncolors); + } + + srcRect.x = 0; + srcRect.y = 0; + srcRect.w = img->extent.width; + srcRect.h = img->extent.height; + pSrcRect = &srcRect; + + targetRect.x = x - img->last_scale_hs.x; + targetRect.y = y - img->last_scale_hs.y; + } + else + { + surf = img->NormalImg; + pSrcRect = NULL; + + targetRect.x = x - img->NormalHs.x; + targetRect.y = y - img->NormalHs.y; + } + + if (cmap) + { + img->colormap_version = cmap->version; + // TODO: Technically, this is not a proper place to release a + // colormap. As it stands now, the colormap must have been + // addrefed when passed to us. + TFB_ReturnColorMap (cmap); + } + + TFB_DrawCanvas_Blit (surf, pSrcRect, target, &targetRect, mode); + UnlockMutex (img->mutex); +} + +// Assumes the source and destination surfaces are in the same format +static void +TFB_DrawCanvas_Fill (SDL_Surface *src, Uint32 fillcolor, SDL_Surface *dst) +{ + const SDL_PixelFormat *srcfmt = src->format; + SDL_PixelFormat *dstfmt = dst->format; + const int width = src->w; + const int height = src->h; + const int bpp = dstfmt->BytesPerPixel; + const int sp = src->pitch, dp = dst->pitch; + const int slen = sp / bpp, dlen = dp / bpp; + const int dsrc = slen - width, ddst = dlen - width; + Uint32 *src_p; + Uint32 *dst_p; + int x, y; + Uint32 srckey = 0, dstkey = 0; // 0 means alpha=0 too + Uint32 amask = srcfmt->Amask; + int alpha = (fillcolor & amask) >> srcfmt->Ashift; + + if (srcfmt->BytesPerPixel != 4 || dstfmt->BytesPerPixel != 4) + { + log_add (log_Warning, "TFB_DrawCanvas_Fill: Unsupported surface " + "formats: %d bytes/pixel source, %d bytes/pixel destination", + (int)srcfmt->BytesPerPixel, (int)dstfmt->BytesPerPixel); + return; + } + + // Strip the alpha channel from fillcolor because we process the + // alpha separately + fillcolor &= ~amask; + + SDL_LockSurface(src); + SDL_LockSurface(dst); + + src_p = (Uint32 *)src->pixels; + dst_p = (Uint32 *)dst->pixels; + + if (dstkey == fillcolor) + { // color collision, must switch colorkey + // new colorkey is grey (1/2,1/2,1/2) + dstkey = SDL_MapRGBA (dstfmt, 127, 127, 127, 0); + } + + if (srcfmt->Amask) + { // alpha-based fill + for (y = 0; y < height; ++y, dst_p += ddst, src_p += dsrc) + { + for (x = 0; x < width; ++x, ++src_p, ++dst_p) + { + Uint32 p = *src_p & amask; + + if (p == 0) + { // fully transparent pixel + *dst_p = dstkey; + } + else if (alpha == 0xff) + { // not for DRAW_ALPHA; use alpha chan directly + *dst_p = p | fillcolor; + } + else + { // for DRAW_ALPHA; modulate the alpha channel + p >>= srcfmt->Ashift; + p = (p * alpha) >> 8; + p <<= srcfmt->Ashift; + *dst_p = p | fillcolor; + } + } + } + } + else if (TFB_GetColorKey (src, &srckey) == 0) + { // colorkey-based fill + + for (y = 0; y < height; ++y, dst_p += ddst, src_p += dsrc) + { + for (x = 0; x < width; ++x, ++src_p, ++dst_p) + { + Uint32 p = *src_p; + + *dst_p = (p == srckey) ? dstkey : fillcolor; + } + } + } + else + { + log_add (log_Warning, "TFB_DrawCanvas_Fill: Unsupported source" + "surface format\n"); + } + + SDL_UnlockSurface(dst); + SDL_UnlockSurface(src); + + // save the colorkey (dynamic image -- not using RLE coding here) + TFB_SetColorKey (dst, dstkey, 0); + // if the filled surface is RGBA, colorkey will only be used + // when SDL_SRCALPHA flag is cleared. this allows us to blit + // the surface in different ways to diff targets +} + +void +TFB_DrawCanvas_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color color, DrawMode mode, TFB_Canvas target) +{ + SDL_Surface *dst = target; + SDL_Rect srcRect, targetRect, *pSrcRect; + SDL_Surface *surf; + SDL_Palette *palette; + int i; + bool force_fill = false; + + if (img == 0) + { + log_add (log_Warning, + "ERROR: TFB_DrawCanvas_FilledImage passed null image ptr"); + return; + } + + checkPrimitiveMode (dst, &color, &mode); + + LockMutex (img->mutex); + + if (scale != 0 && scale != GSCALE_IDENTITY) + { + if (scaleMode == TFB_SCALE_TRILINEAR) + scaleMode = TFB_SCALE_BILINEAR; + // no point in trilinear for filled images + + if (scale != img->last_scale || scaleMode != img->last_scale_type) + force_fill = true; + + TFB_DrawImage_FixScaling (img, scale, scaleMode); + surf = img->ScaledImg; + srcRect.x = 0; + srcRect.y = 0; + srcRect.w = img->extent.width; + srcRect.h = img->extent.height; + pSrcRect = &srcRect; + + targetRect.x = x - img->last_scale_hs.x; + targetRect.y = y - img->last_scale_hs.y; + } + else + { + if (img->last_scale != 0) + { + // Make sure we remember that the last fill was from + // an unscaled image + force_fill = true; + img->last_scale = 0; + } + + surf = img->NormalImg; + pSrcRect = NULL; + + targetRect.x = x - img->NormalHs.x; + targetRect.y = y - img->NormalHs.y; + } + + palette = surf->format->palette; + if (palette) + { // set palette for fill-stamp + // Calling TFB_SetColors() results in an expensive src -> dst + // color-mapping operation for an SDL blit, following the call. + // We want to avoid that as much as possible. + + // TODO: generate a 32bpp filled image? + + SDL_Color colors[256]; + + colors[0] = ColorToNative (color); + for (i = 1; i < palette->ncolors; i++) + colors[i] = colors[0]; + + TFB_SetColors (surf, colors, 0, palette->ncolors); + // reflect the change in *actual* image palette + img->colormap_version--; + } + else + { // fill the non-transparent parts of the image with fillcolor + SDL_Surface *newfill = img->FilledImg; + SDL_PixelFormat *fillfmt; + + if (newfill && (newfill->w < surf->w || newfill->h < surf->h)) + { + TFB_DrawCanvas_Delete (newfill); + newfill = NULL; + } + + // prepare the filled image + if (!newfill) + { + newfill = SDL_CreateRGBSurface (SDL_SWSURFACE, + surf->w, surf->h, + surf->format->BitsPerPixel, + surf->format->Rmask, + surf->format->Gmask, + surf->format->Bmask, + surf->format->Amask); + force_fill = true; + } + fillfmt = newfill->format; + + if (force_fill || !sameColor (img->last_fill, color)) + { // image or fillcolor changed - regenerate + Uint32 fillColor; + + if (mode.kind == DRAW_ALPHA && fillfmt->Amask) + { // Per-pixel alpha and surface alpha will not work together + // We have to handle DRAW_ALPHA differently by modulating + // the surface alpha channel ourselves. + color.a = mode.factor; + mode.kind = DRAW_REPLACE; + } + else + { // Make sure we do not modulate the alpha channel + color.a = 0xff; + } + fillColor = SDL_MapRGBA (newfill->format, color.r, color.g, + color.b, color.a); + TFB_DrawCanvas_Fill (surf, fillColor, newfill); + // cache filled image if possible + img->last_fill = color; + } + + img->FilledImg = newfill; + surf = newfill; + } + + TFB_DrawCanvas_Blit (surf, pSrcRect, dst, &targetRect, mode); + UnlockMutex (img->mutex); +} + +void +TFB_DrawCanvas_FontChar (TFB_Char *fontChar, TFB_Image *backing, + int x, int y, DrawMode mode, TFB_Canvas target) +{ + SDL_Surface *dst = target; + SDL_Rect srcRect, targetRect; + SDL_Surface *surf; + int w, h; + + if (fontChar == 0) + { + log_add (log_Warning, "ERROR: " + "TFB_DrawCanvas_FontChar passed null char ptr"); + return; + } + if (backing == 0) + { + log_add (log_Warning, "ERROR: " + "TFB_DrawCanvas_FontChar passed null backing ptr"); + return; + } + + w = fontChar->extent.width; + h = fontChar->extent.height; + + LockMutex (backing->mutex); + + surf = backing->NormalImg; + if (surf->format->BytesPerPixel != 4 + || surf->w < w || surf->h < h) + { + log_add (log_Warning, "ERROR: " + "TFB_DrawCanvas_FontChar bad backing surface: %dx%dx%d; " + "char: %dx%d", + surf->w, surf->h, (int)surf->format->BytesPerPixel, w, h); + UnlockMutex (backing->mutex); + return; + } + + srcRect.x = 0; + srcRect.y = 0; + srcRect.w = fontChar->extent.width; + srcRect.h = fontChar->extent.height; + + targetRect.x = x - fontChar->HotSpot.x; + targetRect.y = y - fontChar->HotSpot.y; + + // transfer the alpha channel to the backing surface + SDL_LockSurface (surf); + { + int x, y; + const int sskip = fontChar->pitch - w; + const int dskip = (surf->pitch / 4) - w; + const Uint32 dmask = ~surf->format->Amask; + const int ashift = surf->format->Ashift; + Uint8 *src_p = fontChar->data; + Uint32 *dst_p = (Uint32 *)surf->pixels; + + if (mode.kind == DRAW_ALPHA) + { // Per-pixel alpha and surface alpha will not work together + // We have to handle DRAW_ALPHA differently by modulating + // the backing surface alpha channel ourselves. + // The existing backing surface alpha channel is ignored. + int alpha = mode.factor; + mode.kind = DRAW_REPLACE; + + for (y = 0; y < h; ++y, src_p += sskip, dst_p += dskip) + { + for (x = 0; x < w; ++x, ++src_p, ++dst_p) + { + Uint32 p = *dst_p & dmask; + Uint32 a = *src_p; + + // we use >> 8 instead of / 255, and it does not handle + // alpha == 255 correctly + if (alpha != 0xff) + { // modulate the alpha channel + a = (a * alpha) >> 8; + } + *dst_p = p | (a << ashift); + } + } + } + else /* if (mode.kind != DRAW_ALPHA) */ + { // Transfer the alpha channel to the backing surface + // DRAW_REPLACE + Color.a is NOT supported right now + for (y = 0; y < h; ++y, src_p += sskip, dst_p += dskip) + { + for (x = 0; x < w; ++x, ++src_p, ++dst_p) + { + *dst_p = (*dst_p & dmask) | ((Uint32)*src_p << ashift); + } + } + } + } + SDL_UnlockSurface (surf); + + TFB_DrawCanvas_Blit (surf, &srcRect, dst, &targetRect, mode); + UnlockMutex (backing->mutex); +} + +TFB_Canvas +TFB_DrawCanvas_New_TrueColor (int w, int h, BOOLEAN hasalpha) +{ + SDL_Surface *new_surf; + SDL_PixelFormat* fmt = format_conv_surf->format; + + new_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, w, h, + fmt->BitsPerPixel, fmt->Rmask, fmt->Gmask, fmt->Bmask, + hasalpha ? fmt->Amask : 0); + if (!new_surf) + { + log_add (log_Fatal, "INTERNAL PANIC: Failed to create TFB_Canvas: %s", + SDL_GetError()); + exit (EXIT_FAILURE); + } + return new_surf; +} + +TFB_Canvas +TFB_DrawCanvas_New_ForScreen (int w, int h, BOOLEAN withalpha) +{ + SDL_Surface *new_surf; + SDL_PixelFormat* fmt = SDL_Screen->format; + + if (fmt->palette) + { + log_add (log_Warning, "TFB_DrawCanvas_New_ForScreen() WARNING:" + "Paletted display format will be slow"); + + new_surf = TFB_DrawCanvas_New_TrueColor (w, h, withalpha); + } + else + { + if (withalpha && fmt->Amask == 0) + fmt = format_conv_surf->format; // Screen has no alpha and we need it + + new_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, w, h, + fmt->BitsPerPixel, fmt->Rmask, fmt->Gmask, fmt->Bmask, + withalpha ? fmt->Amask : 0); + } + + if (!new_surf) + { + log_add (log_Fatal, "TFB_DrawCanvas_New_ForScreen() INTERNAL PANIC:" + "Failed to create TFB_Canvas: %s", SDL_GetError()); + exit (EXIT_FAILURE); + } + return new_surf; +} + +TFB_Canvas +TFB_DrawCanvas_New_Paletted (int w, int h, Color palette[256], + int transparent_index) +{ + SDL_Surface *new_surf; + new_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, w, h, 8, 0, 0, 0, 0); + if (!new_surf) + { + log_add (log_Fatal, "INTERNAL PANIC: Failed to create TFB_Canvas: %s", + SDL_GetError()); + exit (EXIT_FAILURE); + } + if (palette != NULL) + { + TFB_DrawCanvas_SetPalette (new_surf, palette); + } + if (transparent_index >= 0) + { + TFB_SetColorKey (new_surf, transparent_index, 0); + } + else + { + TFB_DisableColorKey (new_surf); + } + return new_surf; +} + +TFB_Canvas +TFB_DrawCanvas_New_ScaleTarget (TFB_Canvas canvas, TFB_Canvas oldcanvas, int type, int last_type) +{ + SDL_Surface *src = canvas; + SDL_Surface *old = oldcanvas; + SDL_Surface *newsurf = NULL; + + // For the purposes of this function, bilinear == trilinear + if (type == TFB_SCALE_TRILINEAR) + type = TFB_SCALE_BILINEAR; + if (last_type == TFB_SCALE_TRILINEAR) + last_type = TFB_SCALE_BILINEAR; + + if (old && type != last_type) + { + TFB_DrawCanvas_Delete (old); + old = NULL; + } + if (old) + return old; /* can just reuse the old one */ + + if (type == TFB_SCALE_NEAREST) + { + newsurf = SDL_CreateRGBSurface (SDL_SWSURFACE, src->w, + src->h, + src->format->BitsPerPixel, + src->format->Rmask, + src->format->Gmask, + src->format->Bmask, + src->format->Amask); + TFB_DrawCanvas_CopyTransparencyInfo (src, newsurf); + } + else + { + // The scaled image may in fact be larger by 1 pixel than the source + // because of hotspot alignment and fractional edge pixels + if (SDL_Screen->format->BitsPerPixel == 32) + newsurf = TFB_DrawCanvas_New_ForScreen (src->w + 1, src->h + 1, TRUE); + else + newsurf = TFB_DrawCanvas_New_TrueColor (src->w + 1, src->h + 1, TRUE); + } + + return newsurf; +} + +TFB_Canvas +TFB_DrawCanvas_New_RotationTarget (TFB_Canvas src_canvas, int angle) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *newsurf; + EXTENT size; + + TFB_DrawCanvas_GetRotatedExtent (src_canvas, angle, &size); + + newsurf = SDL_CreateRGBSurface (SDL_SWSURFACE, + size.width, size.height, + src->format->BitsPerPixel, + src->format->Rmask, + src->format->Gmask, + src->format->Bmask, + src->format->Amask); + if (!newsurf) + { + log_add (log_Fatal, "TFB_DrawCanvas_New_RotationTarget()" + " INTERNAL PANIC: Failed to create TFB_Canvas: %s", + SDL_GetError()); + exit (EXIT_FAILURE); + } + TFB_DrawCanvas_CopyTransparencyInfo (src, newsurf); + + return newsurf; +} + +TFB_Canvas +TFB_DrawCanvas_LoadFromFile (void *dir, const char *fileName) +{ + SDL_Surface *surf = sdluio_loadImage (dir, fileName); + if (!surf) + return NULL; + + if (surf->format->BitsPerPixel < 8) + { + SDL_SetError ("unsupported image format (min 8bpp)"); + SDL_FreeSurface (surf); + surf = NULL; + } + + return surf; +} + +void +TFB_DrawCanvas_Delete (TFB_Canvas canvas) +{ + if (!canvas) + { + log_add (log_Warning, "INTERNAL PANIC: Attempted" + " to delete a NULL canvas!"); + /* Should we actually die here? */ + } + else + { + SDL_FreeSurface (canvas); + } +} + +BOOLEAN +TFB_DrawCanvas_GetFontCharData (TFB_Canvas canvas, BYTE *outData, + unsigned dataPitch) +{ + SDL_Surface *surf = canvas; + int x, y; + Uint8 r, g, b, a; + Uint32 p; + SDL_PixelFormat *fmt = surf->format; + GetPixelFn getpix; + + if (!surf || !outData) + return FALSE; + + SDL_LockSurface (surf); + + getpix = getpixel_for (surf); + + // produce an alpha-only image in internal BYTE[] format + // from the SDL surface + for (y = 0; y < surf->h; ++y) + { + BYTE *dst = outData + dataPitch * y; + + for (x = 0; x < surf->w; ++x, ++dst) + { + p = getpix (surf, x, y); + SDL_GetRGBA (p, fmt, &r, &g, &b, &a); + + if (!fmt->Amask) + { // produce alpha from intensity (Y component) + // using a fast approximation + // contributions to Y are: R=2, G=4, B=1 + a = ((r * 2) + (g * 4) + b) / 7; + } + + *dst = a; + } + } + + SDL_UnlockSurface (surf); + + return TRUE; +} + +Color * +TFB_DrawCanvas_ExtractPalette (TFB_Canvas canvas) +{ + int i; + Color *result; + SDL_Surface *surf = canvas; + SDL_Palette *palette = surf->format->palette; + + if (!palette) + return NULL; + + // There may be less colors in the surface than 256. Init to 0 first. + result = HCalloc (sizeof (Color) * 256); + assert (palette->ncolors <= 256); + for (i = 0; i < palette->ncolors; ++i) + result[i] = NativeToColor (palette->colors[i]); + + return result; +} + +TFB_Canvas +TFB_DrawCanvas_ToScreenFormat (TFB_Canvas canvas) +{ + SDL_Surface *result = TFB_DisplayFormatAlpha (canvas); + if (result == NULL) + { + log_add (log_Debug, "WARNING: Could not convert" + " sprite-canvas to display format."); + return canvas; + } + else if (result == canvas) + { // no conversion was necessary + return canvas; + } + else + { // converted + TFB_DrawCanvas_Delete (canvas); + return result; + } + + return canvas; +} + +BOOLEAN +TFB_DrawCanvas_IsPaletted (TFB_Canvas canvas) +{ + return ((SDL_Surface *)canvas)->format->palette != NULL; +} + +void +TFB_DrawCanvas_SetPalette (TFB_Canvas target, Color palette[256]) +{ + SDL_Color colors[256]; + int i; + + for (i = 0; i < 256; ++i) + colors[i] = ColorToNative (palette[i]); + + TFB_SetColors (target, colors, 0, 256); +} + +int +TFB_DrawCanvas_GetTransparentIndex (TFB_Canvas canvas) +{ + Uint32 colorkey; + if (TFB_GetColorKey (canvas, &colorkey)) + { + return colorkey; + } + return -1; +} + +void +TFB_DrawCanvas_SetTransparentIndex (TFB_Canvas canvas, int index, BOOLEAN rleaccel) +{ + if (index >= 0) + { + TFB_SetColorKey (canvas, index, rleaccel); + + if (!TFB_DrawCanvas_IsPaletted (canvas)) + { + // disables surface alpha so color key transparency actually works + TFB_DisableSurfaceAlphaMod (canvas); + } + } + else + { + TFB_DisableColorKey (canvas); + } +} + +void +TFB_DrawCanvas_CopyTransparencyInfo (TFB_Canvas src_canvas, + TFB_Canvas dst_canvas) +{ + SDL_Surface* src = src_canvas; + + if (src->format->palette) + { + int index; + index = TFB_DrawCanvas_GetTransparentIndex (src_canvas); + TFB_DrawCanvas_SetTransparentIndex (dst_canvas, index, FALSE); + } + else + { + Color color; + if (TFB_DrawCanvas_GetTransparentColor (src_canvas, &color)) + TFB_DrawCanvas_SetTransparentColor (dst_canvas, color, FALSE); + } +} + +BOOLEAN +TFB_DrawCanvas_GetTransparentColor (TFB_Canvas canvas, Color *color) +{ + Uint32 colorkey; + if (!TFB_DrawCanvas_IsPaletted (canvas) + && (TFB_GetColorKey ((SDL_Surface *)canvas, &colorkey) == 0)) + { + Uint8 ur, ug, ub; + SDL_GetRGB (colorkey, ((SDL_Surface *)canvas)->format, &ur, &ug, &ub); + color->r = ur; + color->g = ug; + color->b = ub; + color->a = 0xff; + return TRUE; + } + return FALSE; +} + +void +TFB_DrawCanvas_SetTransparentColor (TFB_Canvas canvas, Color color, + BOOLEAN rleaccel) +{ + Uint32 sdlColor; + sdlColor = SDL_MapRGBA (((SDL_Surface *)canvas)->format, + color.r, color.g, color.b, 0); + TFB_SetColorKey (canvas, sdlColor, rleaccel); + + if (!TFB_DrawCanvas_IsPaletted (canvas)) + { + // disables surface alpha so color key transparency actually works + TFB_DisableSurfaceAlphaMod (canvas); + } +} + +void +TFB_DrawCanvas_GetScaledExtent (TFB_Canvas src_canvas, HOT_SPOT* src_hs, + TFB_Canvas src_mipmap, HOT_SPOT* mm_hs, + int scale, int type, EXTENT *size, HOT_SPOT *hs) +{ + SDL_Surface *src = src_canvas; + sint32 x, y, w, h; + int frac; + + if (!src_mipmap) + { + w = src->w * scale; + h = src->h * scale; + x = src_hs->x * scale; + y = src_hs->y * scale; + } + else + { + // interpolates extents between src and mipmap to get smoother + // transition when surface changes + SDL_Surface *mipmap = src_mipmap; + int ratio = scale * 2 - GSCALE_IDENTITY; + + assert (scale >= GSCALE_IDENTITY / 2); + + w = mipmap->w * GSCALE_IDENTITY + (src->w - mipmap->w) * ratio; + h = mipmap->h * GSCALE_IDENTITY + (src->h - mipmap->h) * ratio; + + // Seems it is better to use mipmap hotspot because some + // source and mipmap images have the same dimensions! + x = mm_hs->x * GSCALE_IDENTITY + (src_hs->x - mm_hs->x) * ratio; + y = mm_hs->y * GSCALE_IDENTITY + (src_hs->y - mm_hs->y) * ratio; + } + + if (type != TFB_SCALE_NEAREST) + { + // align hotspot on an whole pixel + if (x & (GSCALE_IDENTITY - 1)) + { + frac = GSCALE_IDENTITY - (x & (GSCALE_IDENTITY - 1)); + x += frac; + w += frac; + } + if (y & (GSCALE_IDENTITY - 1)) + { + frac = GSCALE_IDENTITY - (y & (GSCALE_IDENTITY - 1)); + y += frac; + h += frac; + } + // pad the extent to accomodate fractional edge pixels + w += (GSCALE_IDENTITY - 1); + h += (GSCALE_IDENTITY - 1); + } + + size->width = w / GSCALE_IDENTITY; + size->height = h / GSCALE_IDENTITY; + hs->x = x / GSCALE_IDENTITY; + hs->y = y / GSCALE_IDENTITY; + + // Scaled image can be larger than the source by 1 pixel + // because of hotspot alignment and fractional edge pixels + assert (size->width <= src->w + 1 && size->height <= src->h + 1); + + if (!size->width && src->w) + size->width = 1; + if (!size->height && src->h) + size->height = 1; +} + +void +TFB_DrawCanvas_GetExtent (TFB_Canvas canvas, EXTENT *size) +{ + SDL_Surface *src = canvas; + + size->width = src->w; + size->height = src->h; +} + +void +TFB_DrawCanvas_Rescale_Nearest (TFB_Canvas src_canvas, TFB_Canvas dst_canvas, + int scale, HOT_SPOT* src_hs, EXTENT* size, HOT_SPOT* dst_hs) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *dst = dst_canvas; + int x, y; + int fsx = 0, fsy = 0; // source fractional dx and dy increments + int ssx = 0, ssy = 0; // source fractional x and y starting points + int w, h; + + if (scale > 0) + { + TFB_DrawCanvas_GetScaledExtent (src, src_hs, NULL, NULL, scale, + TFB_SCALE_NEAREST, size, dst_hs); + + w = size->width; + h = size->height; + } + else + { + // Just go with the dst surface dimensions + w = dst->w; + h = dst->h; + } + + if (w > dst->w || h > dst->h) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Nearest: Tried to scale" + " image to size %d %d when dest_canvas has only" + " dimensions of %d %d! Failing.", + w, h, dst->w, dst->h); + return; + } + + if (w > 1) + fsx = ((src->w - 1) << 16) / (w - 1); + if (h > 1) + fsy = ((src->h - 1) << 16) / (h - 1); + // We start with a value in 0..0.5 range to shift the bigger + // jumps towards the center of the image + ssx = 0x6000; + ssy = 0x6000; + + SDL_LockSurface (src); + SDL_LockSurface (dst); + + if (src->format->BytesPerPixel == 1 && dst->format->BytesPerPixel == 1) + { + Uint8 *sp, *csp, *dp, *cdp; + int sx, sy; // source fractional x and y positions + + sp = csp = (Uint8 *) src->pixels; + dp = cdp = (Uint8 *) dst->pixels; + + for (y = 0, sy = ssy; y < h; ++y) + { + csp += (sy >> 16) * src->pitch; + sp = csp; + dp = cdp; + for (x = 0, sx = ssx; x < w; ++x) + { + sp += (sx >> 16); + *dp = *sp; + sx &= 0xffff; + sx += fsx; + ++dp; + } + sy &= 0xffff; + sy += fsy; + cdp += dst->pitch; + } + } + else if (src->format->BytesPerPixel == 4 && dst->format->BytesPerPixel == 4) + { + Uint32 *sp, *csp, *dp, *cdp; + int sx, sy; // source fractional x and y positions + int sgap, dgap; + + sgap = src->pitch >> 2; + dgap = dst->pitch >> 2; + + sp = csp = (Uint32 *) src->pixels; + dp = cdp = (Uint32 *) dst->pixels; + + for (y = 0, sy = ssy; y < h; ++y) + { + csp += (sy >> 16) * sgap; + sp = csp; + dp = cdp; + for (x = 0, sx = ssx; x < w; ++x) + { + sp += (sx >> 16); + *dp = *sp; + sx &= 0xffff; + sx += fsx; + ++dp; + } + sy &= 0xffff; + sy += fsy; + cdp += dgap; + } + } + else + { + log_add (log_Warning, "Tried to deal with unknown BPP: %d -> %d", + src->format->BitsPerPixel, dst->format->BitsPerPixel); + } + SDL_UnlockSurface (dst); + SDL_UnlockSurface (src); +} + +typedef union +{ + Uint32 value; + Uint8 chan[4]; + struct + { + Uint8 r, g, b, a; + } c; +} pixel_t; + +static inline Uint8 +dot_product_8_4 (pixel_t* p, int c, Uint8* v) +{ // math expanded for speed +#if 0 + return ( + (Uint32)p[0].chan[c] * v[0] + (Uint32)p[1].chan[c] * v[1] + + (Uint32)p[2].chan[c] * v[2] + (Uint32)p[3].chan[c] * v[3] + ) >> 8; +#else + // mult-table driven version + return + btable[p[0].chan[c]][v[0]] + btable[p[1].chan[c]][v[1]] + + btable[p[2].chan[c]][v[2]] + btable[p[3].chan[c]][v[3]]; +#endif +} + +static inline Uint8 +weight_product_8_4 (pixel_t* p, int c, Uint8* w) +{ // math expanded for speed + return ( + (Uint32)p[0].chan[c] * w[0] + (Uint32)p[1].chan[c] * w[1] + + (Uint32)p[2].chan[c] * w[2] + (Uint32)p[3].chan[c] * w[3] + ) / (w[0] + w[1] + w[2] + w[3]); +} + +static inline Uint8 +blend_ratio_2 (Uint8 c1, Uint8 c2, int ratio) +{ // blend 2 color values according to ratio (0..256) + // identical to proper alpha blending + return (((c1 - c2) * ratio) >> 8) + c2; +} + +static inline Uint32 +scale_read_pixel (void* ppix, SDL_PixelFormat *fmt, SDL_Color *pal, + Uint32 mask, Uint32 key) +{ + pixel_t p; + + // start off with non-present pixel + p.value = 0; + + if (pal) + { // paletted pixel; mask not used + Uint32 c = *(Uint8 *)ppix; + + if (c != key) + { + p.c.r = pal[c].r; + p.c.g = pal[c].g; + p.c.b = pal[c].b; + p.c.a = SDL_ALPHA_OPAQUE; + } + } + else + { // RGB(A) pixel; (pix & mask) != key + Uint32 c = *(Uint32 *)ppix; + + if ((c & mask) != key) + { +#if 0 + SDL_GetRGBA (c, fmt, &p.c.r, &p.c.g, &p.c.b, &p.c.a); +#else + // Assume 8 bits/channel; a safe assumption with 32bpp surfaces + p.c.r = (c >> fmt->Rshift) & 0xff; + p.c.g = (c >> fmt->Gshift) & 0xff; + p.c.b = (c >> fmt->Bshift) & 0xff; + if (fmt->Amask) + p.c.a = (c >> fmt->Ashift) & 0xff; + else + p.c.a = SDL_ALPHA_OPAQUE; + } +#endif + } + + return p.value; +} + +static inline Uint32 +scale_get_pixel (SDL_Surface *src, Uint32 mask, Uint32 key, int x, int y) +{ + SDL_Color *pal = src->format->palette? src->format->palette->colors : 0; + + if (x < 0 || x >= src->w || y < 0 || y >= src->h) + return 0; + + return scale_read_pixel ((Uint8*)src->pixels + y * src->pitch + + x * src->format->BytesPerPixel, src->format, pal, mask, key); +} + +void +TFB_DrawCanvas_Rescale_Trilinear (TFB_Canvas src_canvas, TFB_Canvas src_mipmap, + TFB_Canvas dst_canvas, int scale, HOT_SPOT* src_hs, HOT_SPOT* mm_hs, + EXTENT* size, HOT_SPOT* dst_hs) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *dst = dst_canvas; + SDL_Surface *mm = src_mipmap; + SDL_PixelFormat *srcfmt = src->format; + SDL_PixelFormat *mmfmt = mm->format; + SDL_PixelFormat *dstfmt = dst->format; + SDL_Color *srcpal = srcfmt->palette? srcfmt->palette->colors : 0; + const int sbpp = srcfmt->BytesPerPixel; + const int mmbpp = mmfmt->BytesPerPixel; + const int slen = src->pitch; + const int mmlen = mm->pitch; + const int dst_has_alpha = (dstfmt->Amask != 0); + Uint32 transparent = 0; + const int alpha_threshold = dst_has_alpha ? 0 : 127; + // src v. mipmap importance factor + int ratio = scale * 2 - GSCALE_IDENTITY; + // source masks and keys + Uint32 mk0 = 0, ck0 = ~0, mk1 = 0, ck1 = ~0; + // source fractional x and y positions + int sx0, sy0, sx1, sy1; + // source fractional dx and dy increments + int fsx0 = 0, fsy0 = 0, fsx1 = 0, fsy1 = 0; + // source fractional x and y starting points + int ssx0 = 0, ssy0 = 0, ssx1 = 0, ssy1 = 0; + int x, y, w, h; + + TFB_GetColorKey (dst, &transparent); + + if (mmfmt->palette && !srcpal) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Trilinear: " + "Mipmap is paletted, but source is not! Failing."); + return; + } + + if (scale > 0) + { + int fw, fh; + + // Use (scale / GSCALE_IDENTITY) sizing factor + TFB_DrawCanvas_GetScaledExtent (src, src_hs, mm, mm_hs, scale, + TFB_SCALE_TRILINEAR, size, dst_hs); + + w = size->width; + h = size->height; + + fw = mm->w * GSCALE_IDENTITY + (src->w - mm->w) * ratio; + fh = mm->h * GSCALE_IDENTITY + (src->h - mm->h) * ratio; + + // This limits the effective source dimensions to 2048x2048, + // and we also lose 4 bits of precision out of 16 (no problem) + fsx0 = (src->w << 20) / fw; + fsx0 <<= 4; + fsy0 = (src->h << 20) / fh; + fsy0 <<= 4; + + fsx1 = (mm->w << 20) / fw; + fsx1 <<= 4; + fsy1 = (mm->h << 20) / fh; + fsy1 <<= 4; + + // position the hotspots directly over each other + ssx0 = (src_hs->x << 16) - fsx0 * dst_hs->x; + ssy0 = (src_hs->y << 16) - fsy0 * dst_hs->y; + + ssx1 = (mm_hs->x << 16) - fsx1 * dst_hs->x; + ssy1 = (mm_hs->y << 16) - fsy1 * dst_hs->y; + } + else + { + // Just go with the dst surface dimensions + w = dst->w; + h = dst->h; + + fsx0 = (src->w << 16) / w; + fsy0 = (src->h << 16) / h; + + fsx1 = (mm->w << 16) / w; + fsy1 = (mm->h << 16) / h; + + // give equal importance to both edges + ssx0 = (((src->w - 1) << 16) - fsx0 * (w - 1)) >> 1; + ssy0 = (((src->h - 1) << 16) - fsy0 * (h - 1)) >> 1; + + ssx1 = (((mm->w - 1) << 16) - fsx1 * (w - 1)) >> 1; + ssy1 = (((mm->h - 1) << 16) - fsy1 * (h - 1)) >> 1; + } + + if (w > dst->w || h > dst->h) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Trilinear: " + "Tried to scale image to size %d %d when dest_canvas" + " has only dimensions of %d %d! Failing.", + w, h, dst->w, dst->h); + return; + } + + if ((srcfmt->BytesPerPixel != 1 && srcfmt->BytesPerPixel != 4) || + (mmfmt->BytesPerPixel != 1 && mmfmt->BytesPerPixel != 4) || + (dst->format->BytesPerPixel != 4)) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Trilinear: " + "Tried to deal with unknown BPP: %d -> %d, mipmap %d", + srcfmt->BitsPerPixel, dst->format->BitsPerPixel, + mmfmt->BitsPerPixel); + return; + } + + // use colorkeys where appropriate + if (srcfmt->Amask) + { // alpha transparency + mk0 = srcfmt->Amask; + ck0 = 0; + } + else if (TFB_GetColorKey (src, &ck0) == 0) + { // colorkey transparency + mk0 = ~srcfmt->Amask; + ck0 &= mk0; + } + + if (mmfmt->Amask) + { // alpha transparency + mk1 = mmfmt->Amask; + ck1 = 0; + } + else if (TFB_GetColorKey (mm, &ck1) == 0) + { // colorkey transparency + mk1 = ~mmfmt->Amask; + ck1 &= mk1; + } + + SDL_LockSurface(src); + SDL_LockSurface(dst); + SDL_LockSurface(mm); + + for (y = 0, sy0 = ssy0, sy1 = ssy1; + y < h; + ++y, sy0 += fsy0, sy1 += fsy1) + { + Uint32 *dst_p = (Uint32 *) ((Uint8*)dst->pixels + y * dst->pitch); + const int py0 = (sy0 >> 16); + const int py1 = (sy1 >> 16); + Uint8 *src_a0 = (Uint8*)src->pixels + py0 * slen; + Uint8 *src_a1 = (Uint8*)mm->pixels + py1 * mmlen; + // retrieve the fractional portions of y + const Uint8 v0 = (sy0 >> 8) & 0xff; + const Uint8 v1 = (sy1 >> 8) & 0xff; + Uint8 w0[4], w1[4]; // pixel weight vectors + + for (x = 0, sx0 = ssx0, sx1 = ssx1; + x < w; + ++x, ++dst_p, sx0 += fsx0, sx1 += fsx1) + { + const int px0 = (sx0 >> 16); + const int px1 = (sx1 >> 16); + // retrieve the fractional portions of x + const Uint8 u0 = (sx0 >> 8) & 0xff; + const Uint8 u1 = (sx1 >> 8) & 0xff; + // pixels are examined and numbered in pattern + // 0 1 + // 2 3 + // the ideal pixel (4) is somewhere between these four + // and is calculated from these using weight vector (w) + // with a dot product + pixel_t p0[5], p1[5]; + Uint8 res_a; + + w0[0] = btable[255 - u0][255 - v0]; + w0[1] = btable[u0][255 - v0]; + w0[2] = btable[255 - u0][v0]; + w0[3] = btable[u0][v0]; + + w1[0] = btable[255 - u1][255 - v1]; + w1[1] = btable[u1][255 - v1]; + w1[2] = btable[255 - u1][v1]; + w1[3] = btable[u1][v1]; + + // Collect interesting pixels from src image + // Optimization: speed is criticial on larger images; + // most pixel reads fall completely inside the image + if (px0 >= 0 && px0 + 1 < src->w && py0 >= 0 && py0 + 1 < src->h) + { + Uint8 *src_p = src_a0 + px0 * sbpp; + + p0[0].value = scale_read_pixel (src_p, srcfmt, + srcpal, mk0, ck0); + p0[1].value = scale_read_pixel (src_p + sbpp, srcfmt, + srcpal, mk0, ck0); + p0[2].value = scale_read_pixel (src_p + slen, srcfmt, + srcpal, mk0, ck0); + p0[3].value = scale_read_pixel (src_p + sbpp + slen, srcfmt, + srcpal, mk0, ck0); + } + else + { + p0[0].value = scale_get_pixel (src, mk0, ck0, px0, py0); + p0[1].value = scale_get_pixel (src, mk0, ck0, px0 + 1, py0); + p0[2].value = scale_get_pixel (src, mk0, ck0, px0, py0 + 1); + p0[3].value = scale_get_pixel (src, mk0, ck0, + px0 + 1, py0 + 1); + } + + // Collect interesting pixels from mipmap image + if (px1 >= 0 && px1 + 1 < mm->w && py1 >= 0 && py1 + 1 < mm->h) + { + Uint8 *mm_p = src_a1 + px1 * mmbpp; + + p1[0].value = scale_read_pixel (mm_p, mmfmt, + srcpal, mk1, ck1); + p1[1].value = scale_read_pixel (mm_p + mmbpp, mmfmt, + srcpal, mk1, ck1); + p1[2].value = scale_read_pixel (mm_p + mmlen, mmfmt, + srcpal, mk1, ck1); + p1[3].value = scale_read_pixel (mm_p + mmbpp + mmlen, mmfmt, + srcpal, mk1, ck1); + } + else + { + p1[0].value = scale_get_pixel (mm, mk1, ck1, px1, py1); + p1[1].value = scale_get_pixel (mm, mk1, ck1, px1 + 1, py1); + p1[2].value = scale_get_pixel (mm, mk1, ck1, px1, py1 + 1); + p1[3].value = scale_get_pixel (mm, mk1, ck1, + px1 + 1, py1 + 1); + } + + p0[4].c.a = dot_product_8_4 (p0, 3, w0); + p1[4].c.a = dot_product_8_4 (p1, 3, w1); + + res_a = blend_ratio_2 (p0[4].c.a, p1[4].c.a, ratio); + + if (res_a <= alpha_threshold) + { + *dst_p = transparent; + } + else if (!dst_has_alpha) + { // RGB surface handling + p0[4].c.r = dot_product_8_4 (p0, 0, w0); + p0[4].c.g = dot_product_8_4 (p0, 1, w0); + p0[4].c.b = dot_product_8_4 (p0, 2, w0); + + p1[4].c.r = dot_product_8_4 (p1, 0, w1); + p1[4].c.g = dot_product_8_4 (p1, 1, w1); + p1[4].c.b = dot_product_8_4 (p1, 2, w1); + + p0[4].c.r = blend_ratio_2 (p0[4].c.r, p1[4].c.r, ratio); + p0[4].c.g = blend_ratio_2 (p0[4].c.g, p1[4].c.g, ratio); + p0[4].c.b = blend_ratio_2 (p0[4].c.b, p1[4].c.b, ratio); + + // TODO: we should handle alpha-blending here, but we do + // not know the destination color for blending! + + *dst_p = + (p0[4].c.r << dstfmt->Rshift) | + (p0[4].c.g << dstfmt->Gshift) | + (p0[4].c.b << dstfmt->Bshift); + } + else + { // RGBA surface handling + + // we do not want to blend with non-present pixels + // (pixels that have alpha == 0) as these will + // skew the result and make resulting alpha useless + if (p0[4].c.a != 0) + { + int i; + for (i = 0; i < 4; ++i) + if (p0[i].c.a == 0) + w0[i] = 0; + + p0[4].c.r = weight_product_8_4 (p0, 0, w0); + p0[4].c.g = weight_product_8_4 (p0, 1, w0); + p0[4].c.b = weight_product_8_4 (p0, 2, w0); + } + if (p1[4].c.a != 0) + { + int i; + for (i = 0; i < 4; ++i) + if (p1[i].c.a == 0) + w1[i] = 0; + + p1[4].c.r = weight_product_8_4 (p1, 0, w1); + p1[4].c.g = weight_product_8_4 (p1, 1, w1); + p1[4].c.b = weight_product_8_4 (p1, 2, w1); + } + + if (p0[4].c.a != 0 && p1[4].c.a != 0) + { // blend if both present + p0[4].c.r = blend_ratio_2 (p0[4].c.r, p1[4].c.r, ratio); + p0[4].c.g = blend_ratio_2 (p0[4].c.g, p1[4].c.g, ratio); + p0[4].c.b = blend_ratio_2 (p0[4].c.b, p1[4].c.b, ratio); + } + else if (p1[4].c.a != 0) + { // other pixel is present + p0[4].value = p1[4].value; + } + + // error-correct alpha to fully opaque to remove + // the often unwanted and unnecessary blending + if (res_a > 0xf8) + res_a = 0xff; + + *dst_p = + (p0[4].c.r << dstfmt->Rshift) | + (p0[4].c.g << dstfmt->Gshift) | + (p0[4].c.b << dstfmt->Bshift) | + (res_a << dstfmt->Ashift); + } + } + } + + SDL_UnlockSurface(mm); + SDL_UnlockSurface(dst); + SDL_UnlockSurface(src); +} + +void +TFB_DrawCanvas_Rescale_Bilinear (TFB_Canvas src_canvas, TFB_Canvas dst_canvas, + int scale, HOT_SPOT* src_hs, EXTENT* size, HOT_SPOT* dst_hs) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *dst = dst_canvas; + SDL_PixelFormat *srcfmt = src->format; + SDL_PixelFormat *dstfmt = dst->format; + SDL_Color *srcpal = srcfmt->palette? srcfmt->palette->colors : 0; + const int sbpp = srcfmt->BytesPerPixel; + const int slen = src->pitch; + const int dst_has_alpha = (dstfmt->Amask != 0); + Uint32 srckey = 0, transparent = 0; + const int alpha_threshold = dst_has_alpha ? 0 : 127; + // source masks and keys + Uint32 mk = 0, ck = ~0; + // source fractional x and y positions + int sx, sy; + // source fractional dx and dy increments + int fsx = 0, fsy = 0; + // source fractional x and y starting points + int ssx = 0, ssy = 0; + int x, y, w, h; + + // Get destination transparent color if it exists + TFB_GetColorKey (dst, &transparent); + + if (scale > 0) + { + // Use (scale / GSCALE_IDENTITY) sizing factor + TFB_DrawCanvas_GetScaledExtent (src, src_hs, NULL, NULL, scale, + TFB_SCALE_BILINEAR, size, dst_hs); + + w = size->width; + h = size->height; + fsx = (GSCALE_IDENTITY << 16) / scale; + fsy = (GSCALE_IDENTITY << 16) / scale; + + // position the hotspots directly over each other + ssx = (src_hs->x << 16) - fsx * dst_hs->x; + ssy = (src_hs->y << 16) - fsy * dst_hs->y; + } + else + { + // Just go with the dst surface dimensions + w = dst->w; + h = dst->h; + fsx = (src->w << 16) / w; + fsy = (src->h << 16) / h; + + // give equal importance to both edges + ssx = (((src->w - 1) << 16) - fsx * (w - 1)) >> 1; + ssy = (((src->h - 1) << 16) - fsy * (h - 1)) >> 1; + } + + if (w > dst->w || h > dst->h) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Bilinear: " + "Tried to scale image to size %d %d when dest_canvas" + " has only dimensions of %d %d! Failing.", + w, h, dst->w, dst->h); + return; + } + + if ((srcfmt->BytesPerPixel != 1 && srcfmt->BytesPerPixel != 4) || + (dst->format->BytesPerPixel != 4)) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Bilinear: " + "Tried to deal with unknown BPP: %d -> %d", + srcfmt->BitsPerPixel, dst->format->BitsPerPixel); + return; + } + + // use colorkeys where appropriate + if (srcfmt->Amask) + { // alpha transparency + mk = srcfmt->Amask; + ck = 0; + } + else if (TFB_GetColorKey (src, &srckey) == 0) + { // colorkey transparency + mk = ~srcfmt->Amask; + ck = srckey & mk; + } + + SDL_LockSurface(src); + SDL_LockSurface(dst); + + for (y = 0, sy = ssy; y < h; ++y, sy += fsy) + { + Uint32 *dst_p = (Uint32 *) ((Uint8*)dst->pixels + y * dst->pitch); + const int py = (sy >> 16); + Uint8 *src_a = (Uint8*)src->pixels + py * slen; + // retrieve the fractional portions of y + const Uint8 v = (sy >> 8) & 0xff; + Uint8 weight[4]; // pixel weight vectors + + for (x = 0, sx = ssx; x < w; ++x, ++dst_p, sx += fsx) + { + const int px = (sx >> 16); + // retrieve the fractional portions of x + const Uint8 u = (sx >> 8) & 0xff; + // pixels are examined and numbered in pattern + // 0 1 + // 2 3 + // the ideal pixel (4) is somewhere between these four + // and is calculated from these using weight vector (weight) + // with a dot product + pixel_t p[5]; + + weight[0] = btable[255 - u][255 - v]; + weight[1] = btable[u][255 - v]; + weight[2] = btable[255 - u][v]; + weight[3] = btable[u][v]; + + // Collect interesting pixels from src image + // Optimization: speed is criticial on larger images; + // most pixel reads fall completely inside the image + if (px >= 0 && px + 1 < src->w && py >= 0 && py + 1 < src->h) + { + Uint8 *src_p = src_a + px * sbpp; + + p[0].value = scale_read_pixel (src_p, srcfmt, srcpal, mk, ck); + p[1].value = scale_read_pixel (src_p + sbpp, srcfmt, + srcpal, mk, ck); + p[2].value = scale_read_pixel (src_p + slen, srcfmt, + srcpal, mk, ck); + p[3].value = scale_read_pixel (src_p + sbpp + slen, srcfmt, + srcpal, mk, ck); + } + else + { + p[0].value = scale_get_pixel (src, mk, ck, px, py); + p[1].value = scale_get_pixel (src, mk, ck, px + 1, py); + p[2].value = scale_get_pixel (src, mk, ck, px, py + 1); + p[3].value = scale_get_pixel (src, mk, ck, px + 1, py + 1); + } + + p[4].c.a = dot_product_8_4 (p, 3, weight); + + if (p[4].c.a <= alpha_threshold) + { + *dst_p = transparent; + } + else if (!dst_has_alpha) + { // RGB surface handling + p[4].c.r = dot_product_8_4 (p, 0, weight); + p[4].c.g = dot_product_8_4 (p, 1, weight); + p[4].c.b = dot_product_8_4 (p, 2, weight); + + // TODO: we should handle alpha-blending here, but we do + // not know the destination color for blending! + + *dst_p = + (p[4].c.r << dstfmt->Rshift) | + (p[4].c.g << dstfmt->Gshift) | + (p[4].c.b << dstfmt->Bshift); + } + else + { // RGBA surface handling + + // we do not want to blend with non-present pixels + // (pixels that have alpha == 0) as these will + // skew the result and make resulting alpha useless + int i; + for (i = 0; i < 4; ++i) + if (p[i].c.a == 0) + weight[i] = 0; + + p[4].c.r = weight_product_8_4 (p, 0, weight); + p[4].c.g = weight_product_8_4 (p, 1, weight); + p[4].c.b = weight_product_8_4 (p, 2, weight); + + // error-correct alpha to fully opaque to remove + // the often unwanted and unnecessary blending + if (p[4].c.a > 0xf8) + p[4].c.a = 0xff; + + *dst_p = + (p[4].c.r << dstfmt->Rshift) | + (p[4].c.g << dstfmt->Gshift) | + (p[4].c.b << dstfmt->Bshift) | + (p[4].c.a << dstfmt->Ashift); + } + } + } + + SDL_UnlockSurface(dst); + SDL_UnlockSurface(src); +} + +void +TFB_DrawCanvas_Lock (TFB_Canvas canvas) +{ + SDL_Surface *surf = canvas; + SDL_LockSurface (surf); +} + +void +TFB_DrawCanvas_Unlock (TFB_Canvas canvas) +{ + SDL_Surface *surf = canvas; + SDL_UnlockSurface (surf); +} + +void +TFB_DrawCanvas_GetScreenFormat (TFB_PixelFormat *fmt) +{ + SDL_PixelFormat *sdl = SDL_Screen->format; + + if (sdl->palette) + { + log_add (log_Warning, "TFB_DrawCanvas_GetScreenFormat() WARNING:" + "Paletted display format will be slow"); + + fmt->BitsPerPixel = 32; + fmt->Rmask = 0x000000ff; + fmt->Gmask = 0x0000ff00; + fmt->Bmask = 0x00ff0000; + fmt->Amask = 0xff000000; + } + else + { + fmt->BitsPerPixel = sdl->BitsPerPixel; + fmt->Rmask = sdl->Rmask; + fmt->Gmask = sdl->Gmask; + fmt->Bmask = sdl->Bmask; + fmt->Amask = sdl->Amask; + } +} + +int +TFB_DrawCanvas_GetStride (TFB_Canvas canvas) +{ + SDL_Surface *surf = canvas; + return surf->pitch; +} + +void* +TFB_DrawCanvas_GetLine (TFB_Canvas canvas, int line) +{ + SDL_Surface *surf = canvas; + return (uint8 *)surf->pixels + surf->pitch * line; +} + +Color +TFB_DrawCanvas_GetPixel (TFB_Canvas canvas, int x, int y) +{ + SDL_Surface* surf = canvas; + Uint32 pixel; + GetPixelFn getpixel; + Color c = {0, 0, 0, 0}; + + if (x < 0 || x >= surf->w || y < 0 || y >= surf->h) + { // outside bounds, return 0 + return c; + } + + SDL_LockSurface (surf); + + getpixel = getpixel_for(surf); + pixel = (*getpixel)(surf, x, y); + SDL_GetRGBA (pixel, surf->format, &c.r, &c.g, &c.b, &c.a); + + SDL_UnlockSurface (surf); + + return c; +} + +void +TFB_DrawCanvas_Rotate (TFB_Canvas src_canvas, TFB_Canvas dst_canvas, + int angle, EXTENT size) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *dst = dst_canvas; + int ret; + Color color; + + if (size.width > dst->w || size.height > dst->h) + { + log_add (log_Warning, "TFB_DrawCanvas_Rotate: Tried to rotate" + " image to size %d %d when dst_canvas has only dimensions" + " of %d %d! Failing.", + size.width, size.height, dst->w, dst->h); + return; + } + + if (TFB_DrawCanvas_GetTransparentColor (src, &color)) + { + TFB_DrawCanvas_SetTransparentColor (dst, color, FALSE); + /* fill destination with transparent color before rotating */ + SDL_FillRect(dst, NULL, SDL_MapRGBA (dst->format, + color.r, color.g, color.b, 0)); + } + + ret = rotateSurface (src, dst, angle, 0); + if (ret != 0) + { + log_add (log_Warning, "TFB_DrawCanvas_Rotate: WARNING:" + " actual rotation func returned failure\n"); + } +} + +void +TFB_DrawCanvas_GetRotatedExtent (TFB_Canvas src_canvas, int angle, EXTENT *size) +{ + int dstw, dsth; + SDL_Surface *src = src_canvas; + + rotozoomSurfaceSize (src->w, src->h, angle, 1, &dstw, &dsth); + size->height = dsth; + size->width = dstw; +} + +void +TFB_DrawCanvas_CopyRect (TFB_Canvas source, const RECT *srcRect, + TFB_Canvas target, POINT dstPt) +{ + SDL_Rect sourceRect, targetRect; + + if (source == 0 || target == 0) + { + log_add (log_Warning, + "ERROR: TFB_DrawCanvas_CopyRect passed null canvas ptr"); + return; + } + + sourceRect.x = srcRect->corner.x; + sourceRect.y = srcRect->corner.y; + sourceRect.w = srcRect->extent.width; + sourceRect.h = srcRect->extent.height; + + targetRect.x = dstPt.x; + targetRect.y = dstPt.y; + // According to SDL docs, width and height are ignored, but + // we'll set them anyway, just in case. + targetRect.w = srcRect->extent.width; + targetRect.h = srcRect->extent.height; + + SDL_BlitSurface (source, &sourceRect, target, &targetRect); +} + +void +TFB_DrawCanvas_SetClipRect (TFB_Canvas canvas, const RECT *clipRect) +{ + if (canvas == 0) + { + log_add (log_Warning, + "ERROR: TFB_DrawCanvas_SetClipRect passed null canvas ptr"); + return; + } + + if (!clipRect) + { // clipping disabled + SDL_SetClipRect (canvas, NULL); + } + else + { + SDL_Rect r; + r.x = clipRect->corner.x; + r.y = clipRect->corner.y; + r.w = clipRect->extent.width; + r.h = clipRect->extent.height; + SDL_SetClipRect (canvas, &r); + } +} + +BOOLEAN +TFB_DrawCanvas_Intersect (TFB_Canvas canvas1, POINT c1org, + TFB_Canvas canvas2, POINT c2org, const RECT *interRect) +{ + BOOLEAN ret = FALSE; + SDL_Surface *surf1 = canvas1; + SDL_Surface *surf2 = canvas2; + int x, y; + Uint32 s1key, s2key; + Uint32 s1mask, s2mask; + GetPixelFn getpixel1, getpixel2; + + SDL_LockSurface (surf1); + SDL_LockSurface (surf2); + + getpixel1 = getpixel_for (surf1); + getpixel2 = getpixel_for (surf2); + + if (surf1->format->Amask) + { // use alpha transparency info + s1mask = surf1->format->Amask; + // consider any not fully transparent pixel collidable + s1key = 0; + } + else + { // colorkey transparency + Uint32 colorkey = 0; + TFB_GetColorKey(surf1, &colorkey); + s1mask = ~surf1->format->Amask; + s1key = colorkey & s1mask; + } + + if (surf2->format->Amask) + { // use alpha transparency info + s2mask = surf2->format->Amask; + // consider any not fully transparent pixel collidable + s2key = 0; + } + else + { // colorkey transparency + Uint32 colorkey = 0; + TFB_GetColorKey(surf2, &colorkey); + s2mask = ~surf2->format->Amask; + s2key = colorkey & s2mask; + } + + // convert surface origins to pixel offsets within + c1org.x = interRect->corner.x - c1org.x; + c1org.y = interRect->corner.y - c1org.y; + c2org.x = interRect->corner.x - c2org.x; + c2org.y = interRect->corner.y - c2org.y; + + for (y = 0; y < interRect->extent.height; ++y) + { + for (x = 0; x < interRect->extent.width; ++x) + { + Uint32 p1 = getpixel1 (surf1, x + c1org.x, y + c1org.y) & s1mask; + Uint32 p2 = getpixel2 (surf2, x + c2org.x, y + c2org.y) & s2mask; + + if (p1 != s1key && p2 != s2key) + { // pixel collision + ret = TRUE; + break; + } + } + } + + SDL_UnlockSurface (surf2); + SDL_UnlockSurface (surf1); + + return ret; +} + +// Read/write the canvas pixels in a Color format understood by the core. +// The pixels array is assumed to be at least width * height large. +// The pixels array can be wider/narrower or taller/shorter than the canvas, +// and in that case, only the relevant pixels will be transfered. +static BOOLEAN +TFB_DrawCanvas_TransferColors (TFB_Canvas canvas, BOOLEAN write, + Color *pixels, int width, int height) +{ + SDL_Surface *surf = canvas; + SDL_PixelFormat *fmt; + GetPixelFn getpix; + PutPixelFn putpix; + int x, y, w, h; + + if (canvas == 0) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_TransferColors " + "passed null canvas"); + return FALSE; + } + + fmt = surf->format; + getpix = getpixel_for (surf); + putpix = putpixel_for (surf); + + w = width < surf->w ? width : surf->w; + h = height < surf->h ? height : surf->h; + + SDL_LockSurface (surf); + + // This could be done faster if we assumed 32bpp surfaces + for (y = 0; y < h; ++y) + { + // pixels array pitch is width so as not to violate the interface + Color *c = pixels + y * width; + + for (x = 0; x < w; ++x, ++c) + { + if (write) + { // writing from data to surface + Uint32 p = SDL_MapRGBA (fmt, c->r, c->g, c->b, c->a); + putpix (surf, x, y, p); + } + else + { // reading from surface to data + Uint32 p = getpix (surf, x, y); + SDL_GetRGBA (p, fmt, &c->r, &c->g, &c->b, &c->a); + } + } + } + + SDL_UnlockSurface (surf); + + return TRUE; +} + +// Read the canvas pixels in a Color format understood by the core. +// See TFB_DrawCanvas_TransferColors() for pixels array info +BOOLEAN +TFB_DrawCanvas_GetPixelColors (TFB_Canvas canvas, Color *pixels, + int width, int height) +{ + return TFB_DrawCanvas_TransferColors (canvas, FALSE, pixels, + width, height); +} + +// Write the canvas pixels from a Color format understood by the core. +// See TFB_DrawCanvas_TransferColors() for pixels array info +BOOLEAN +TFB_DrawCanvas_SetPixelColors (TFB_Canvas canvas, const Color *pixels, + int width, int height) +{ + // unconst pixels, but it is safe -- it will not be written to + return TFB_DrawCanvas_TransferColors (canvas, TRUE, (Color *)pixels, + width, height); +} + +// Read/write the indexed canvas pixels as palette indexes. +// The data array is assumed to be at least width * height large. +// The data array can be wider/narrower or taller/shorter than the canvas, +// and in that case, only the relevant pixels will be transfered. +static BOOLEAN +TFB_DrawCanvas_TransferIndexes (TFB_Canvas canvas, BOOLEAN write, + BYTE *data, int width, int height) +{ + SDL_Surface *surf = canvas; + const SDL_PixelFormat *fmt; + int y, w, h; + + if (canvas == 0) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_TransferIndexes " + "passed null canvas"); + return FALSE; + } + fmt = surf->format; + if (!TFB_DrawCanvas_IsPaletted (canvas) || fmt->BitsPerPixel != 8) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_TransferIndexes " + "unimplemeted function: not an 8bpp indexed canvas"); + return FALSE; + } + + w = width < surf->w ? width : surf->w; + h = height < surf->h ? height : surf->h; + + SDL_LockSurface (surf); + + for (y = 0; y < h; ++y) + { + Uint8 *surf_p = (Uint8 *)surf->pixels + y * surf->pitch; + // pixels array pitch is width so as not to violate the interface + BYTE *data_p = data + y * width; + + if (write) + { // writing from data to surface + memcpy (surf_p, data_p, w * sizeof (BYTE)); + } + else + { // reading from surface to data + memcpy (data_p, surf_p, w * sizeof (BYTE)); + } + } + + SDL_UnlockSurface (surf); + + return TRUE; +} + +// Read the indexed canvas pixels as palette indexes. +// See TFB_DrawCanvas_TransferIndexes() for data array info. +BOOLEAN +TFB_DrawCanvas_GetPixelIndexes (TFB_Canvas canvas, BYTE *data, + int width, int height) +{ + return TFB_DrawCanvas_TransferIndexes (canvas, FALSE, data, + width, height); +} + +// Write the indexed canvas pixels as palette indexes. +// See TFB_DrawCanvas_TransferIndexes() for data array info. +BOOLEAN +TFB_DrawCanvas_SetPixelIndexes (TFB_Canvas canvas, const BYTE *data, + int width, int height) +{ + // unconst data, but it is safe -- it will not be written to + return TFB_DrawCanvas_TransferIndexes (canvas, TRUE, (BYTE *)data, + width, height); +} |