summaryrefslogtreecommitdiff
path: root/src/libs/graphics/sdl/canvas.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/graphics/sdl/canvas.c')
-rw-r--r--src/libs/graphics/sdl/canvas.c2176
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);
+}