aboutsummaryrefslogtreecommitdiff
path: root/project/src/lib-image3.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--project/src/lib-image3.c2219
1 files changed, 2219 insertions, 0 deletions
diff --git a/project/src/lib-image3.c b/project/src/lib-image3.c
new file mode 100644
index 0000000..2f04abb
--- /dev/null
+++ b/project/src/lib-image3.c
@@ -0,0 +1,2219 @@
+
+/***************************************************************************
+ * _______ _______ _______ *
+ * | ___ |____ | ___ | *
+ * | | |_| / /| | |_| *
+ * | |_____ / / | |_____ *
+ * |_____ | / / |_____ | *
+ * _ | | / / _ | | *
+ * | |___| |/ /____| |___| | *
+ * |_______|_______|_______| *
+ * *
+ * Wiimms SZS Tools *
+ * https://szs.wiimm.de/ *
+ * *
+ ***************************************************************************
+ * *
+ * This file is part of the SZS project. *
+ * Visit https://szs.wiimm.de/ for project details and sources. *
+ * *
+ * Copyright (c) 2011-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * 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. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#include "lib-std.h"
+#include "lib-image.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// data structures ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct PatchRange1_t
+{
+ int dest; // destination coordinate
+ int src; // source coordinate
+ int len; // length of copy operation
+ int size; // total number of pixels
+
+} PatchRange1_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct PatchRange2_t
+{
+ PatchRange1_t x; // horizontal values
+ PatchRange1_t y; // vertical values
+
+} PatchRange2_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// image creation + drawing ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CreateIMG
+(
+ Image_t * img, // image object
+ bool init_img, // true: initialize 'img' first
+ uint width, // width a new image
+ uint height, // height of new image
+ Color_t col // fill color
+)
+{
+ DASSERT(img);
+
+ if (init_img)
+ InitializeIMG(img);
+
+ if (!width)
+ width = 1;
+ const uint xwidth = EXPAND8(width);
+
+ if (!height)
+ height = 1;
+ const uint xheight = EXPAND8(height);
+
+ uint size = xwidth * xheight;
+ Color_t *data = MALLOC(size*sizeof(col));
+
+ img->width = width;
+ img->height = height;
+ AssignDataRGB(img,0,(u8*)data);
+ while ( size-- > 0 )
+ *data++ = col;
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CopyRectIMG
+(
+ Image_t * dest, // dest image
+ int dest_x, // left coordinate of dest
+ int dest_y, // top coordinate of dest
+
+ const Image_t * src, // source image
+ int src_x, // left coordinate of src
+ int src_y, // top coordinate of src
+
+ int width, // width of rect to copy
+ int height, // height of rect to copy
+
+ PatchImage_t patch_mode // one of COPY, MIX, FOREGROUND, BACKGROUND
+)
+{
+ DASSERT(dest);
+ DASSERT(src);
+
+ if ( dest->iform != IMG_X_RGB )
+ ConvertIMG(dest,false,0,IMG_X_RGB,PAL_AUTO);
+ DASSERT(dest->iform == IMG_X_RGB);
+
+ if ( src->iform != IMG_X_RGB )
+ return ERROR0(ERR_INTERNAL,0);
+
+
+ //--- normalize horizontal values
+
+ if ( dest_x < 0 )
+ {
+ width += dest_x;
+ src_x -= dest_x;
+ dest_x = 0;
+ }
+
+ if ( src_x < 0 )
+ {
+ width += src_x;
+ dest_x -= src_x;
+ src_x = 0;
+ }
+
+ int max = dest->width - dest_x;
+ if ( width > max )
+ width = max;
+
+ max = src->width - src_x;
+ if ( width > max )
+ width = max;
+
+
+ //--- normalize vertical values
+
+ if ( dest_y < 0 )
+ {
+ height += dest_y;
+ src_y -= dest_y;
+ dest_y = 0;
+ }
+
+ if ( src_y < 0 )
+ {
+ height += src_y;
+ dest_y -= src_y;
+ src_y = 0;
+ }
+
+ max = dest->height - dest_y;
+ if ( height > max )
+ height = max;
+
+ max = src->height - src_y;
+ if ( height > max )
+ height = max;
+
+
+ //--- copy rect
+
+ if ( width > 0 && height > 0 )
+ {
+ DASSERT( dest_x >= 0 && dest_x + width <= dest->width );
+ DASSERT( dest_y >= 0 && dest_y + height <= dest->height );
+ DASSERT( src_x >= 0 && src_x + width <= src->width );
+ DASSERT( src_y >= 0 && src_y + height <= src->height );
+
+ Color_t *dcol = (Color_t*)dest->data + dest_y * dest->xwidth + dest_x;
+ const Color_t *scol = (Color_t*)src->data + src_y * src->xwidth + src_x;
+
+ const uint dest_skip = dest->xwidth - width;
+ const uint src_skip = src->xwidth - width;
+
+ switch ( patch_mode & PIM_M_COPY )
+ {
+ case PIM_BACKGROUND:
+ while ( height-- > 0 )
+ {
+ uint n = width;
+ while ( n-- > 0 )
+ {
+ const uint dalpha = dcol->a;
+ const uint salpha = 0xff - dalpha;
+ dcol->r = ( dalpha * dcol->r + salpha * scol->r + 0x80 ) / 0xff;
+ dcol->g = ( dalpha * dcol->g + salpha * scol->g + 0x80 ) / 0xff;
+ dcol->b = ( dalpha * dcol->b + salpha * scol->b + 0x80 ) / 0xff;
+ dcol->a = ( 0xfe01 - ( 0xff - dcol->a ) * ( 0xff - scol->a )) / 0xff;
+ dcol++;
+ scol++;
+ }
+ DASSERT( (u8*)dcol <= dest->data + dest->data_size );
+ DASSERT( (u8*)scol <= src->data + src->data_size );
+ dcol += dest_skip;
+ scol += src_skip;
+ }
+ break;
+
+ case PIM_FOREGROUND:
+ while ( height-- > 0 )
+ {
+ uint n = width;
+ while ( n-- > 0 )
+ {
+ const uint salpha = scol->a;
+ const uint dalpha = 0xff - salpha;
+ dcol->r = ( dalpha * dcol->r + salpha * scol->r + 0x80 ) / 0xff;
+ dcol->g = ( dalpha * dcol->g + salpha * scol->g + 0x80 ) / 0xff;
+ dcol->b = ( dalpha * dcol->b + salpha * scol->b + 0x80 ) / 0xff;
+ dcol->a = ( 0xfe01 - ( 0xff - dcol->a ) * ( 0xff - scol->a )) / 0xff;
+ dcol++;
+ scol++;
+ }
+ DASSERT( (u8*)dcol <= dest->data + dest->data_size );
+ DASSERT( (u8*)scol <= src->data + src->data_size );
+ dcol += dest_skip;
+ scol += src_skip;
+ }
+ break;
+
+ case PIM_MIX:
+ while ( height-- > 0 )
+ {
+ uint n = width;
+ while ( n-- > 0 )
+ {
+ const uint alpha = dcol->a + scol->a;
+ if (alpha)
+ {
+ const uint alpha2 = alpha/2;
+ dcol->r = ( dcol->a * dcol->r + scol->a * scol->r + alpha2 ) / alpha;
+ dcol->g = ( dcol->a * dcol->g + scol->a * scol->g + alpha2 ) / alpha;
+ dcol->b = ( dcol->a * dcol->b + scol->a * scol->b + alpha2 ) / alpha;
+ dcol->a = ( 0xfe01 - ( 0xff - dcol->a ) * ( 0xff - scol->a )) / 0xff;
+ }
+ dcol++;
+ scol++;
+ }
+ DASSERT( (u8*)dcol <= dest->data + dest->data_size );
+ DASSERT( (u8*)scol <= src->data + src->data_size );
+ dcol += dest_skip;
+ scol += src_skip;
+ }
+ break;
+
+ default: // PIM_COPY:
+ while ( height-- > 0 )
+ {
+ uint n = width;
+ while ( n-- > 0 )
+ *dcol++ = *scol++;
+ DASSERT( (u8*)dcol <= dest->data + dest->data_size );
+ DASSERT( (u8*)scol <= src->data + src->data_size );
+ dcol += dest_skip;
+ scol += src_skip;
+ }
+ break;
+ }
+ }
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError DrawPointIMG
+(
+ Image_t * img, // image
+ int x, // x position (robust)
+ int y, // y position (robust)
+ Color_t col, // draw color
+ bool combine // true: combine 'col' with current one
+)
+{
+ DASSERT(img);
+ if ( img->iform != IMG_X_RGB )
+ ConvertIMG(img,false,0,IMG_X_RGB,PAL_AUTO);
+
+ if ( x >= 0 && x < img->width && y >= 0 && y < img->height && col.a )
+ {
+ Color_t *dest = (Color_t*)img->data + y * img->xwidth + x;
+
+ if ( combine && col.a != 0xff )
+ {
+ const uint da = ( 0xff - col.a ) * dest->a;
+ dest->r = ( da * dest->r + 0xff * col.a * col.r ) / 0xfe01;
+ dest->g = ( da * dest->g + 0xff * col.a * col.g ) / 0xfe01;
+ dest->b = ( da * dest->b + 0xff * col.a * col.b ) / 0xfe01;
+ dest->a = 0xff - (0xff-dest->a)*(0xff-col.a)/0xff;
+ }
+ else
+ *dest = col;
+ }
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError DrawHLineIMG
+(
+ Image_t * img, // image
+ int x1, // start point (robust)
+ int x2, // end point (not drawn, robust)
+ int y, // common y coordinate (robust)
+ Color_t col // draw color
+)
+{
+ DASSERT(img);
+ if ( img->iform != IMG_X_RGB )
+ ConvertIMG(img,false,0,IMG_X_RGB,PAL_AUTO);
+
+ if ( x1 > x2 )
+ {
+ const uint temp = x1;
+ x1 = x2 - 1;
+ x2 = temp + 1;
+ }
+
+ if ( x1 < 0 )
+ x1 = 0;
+ if ( x2 > img->width )
+ x2 = img->width;
+
+ if ( x1 < x2 && y >= 0 && y < img->height )
+ {
+ Color_t *dest = (Color_t*)img->data + y * img->xwidth + x1;
+ uint n = x2 - x1;
+ while ( n-- > 0 )
+ *dest++ = col;
+ DASSERT( (u8*)dest <= img->data + img->data_size );
+ }
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError DrawVLineIMG
+(
+ Image_t * img, // image
+ int x, // common x coordinate (robust)
+ int y1, // start point (robust)
+ int y2, // end point (not drawn, robust)
+ Color_t col // draw color
+)
+{
+ DASSERT(img);
+ if ( img->iform != IMG_X_RGB )
+ ConvertIMG(img,false,0,IMG_X_RGB,PAL_AUTO);
+
+ if ( y1 > y2 )
+ {
+ const uint temp = y1;
+ y1 = y2 - 1;
+ y2 = temp + 1;
+ }
+
+ if ( y1 < 0 )
+ y1 = 0;
+ if ( y2 > img->height )
+ y2 = img->height;
+
+ if ( y1 < y2 && x >= 0 && x < img->width )
+ {
+ Color_t *dest = (Color_t*)img->data + y1 * img->xwidth + x;
+ uint n = y2 - y1;
+ while ( n-- > 0 )
+ {
+ *dest = col;
+ dest += img->xwidth;
+ }
+ DASSERT( (u8*)dest <= img->data + img->data_size );
+ }
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError DrawFrameIMG
+(
+ Image_t * img, // image
+ int x1, // left coordinate (robust)
+ int y1, // top coordinate (robust)
+ int x2, // right coordinate (robust)
+ int y2, // bottom coordinate (robust)
+ Color_t col // draw color
+)
+{
+ DASSERT(img);
+ DrawHLineIMG(img,x1,x2,y1,col);
+ DrawVLineIMG(img,x2,y1,y2,col);
+ DrawHLineIMG(img,x2,x1,y2,col);
+ DrawVLineIMG(img,x1,y2,y1,col);
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError FillRectIMG
+(
+ Image_t * img, // image
+ int x1, // left coordinate (robust)
+ int y1, // top coordinate (robust)
+ int x2, // right coordinate (robust)
+ int y2, // bottom coordinate (robust)
+ Color_t col, // draw color
+ bool combine // true: combine 'col' with current one
+)
+{
+ DASSERT(img);
+ if ( img->iform != IMG_X_RGB )
+ ConvertIMG(img,false,0,IMG_X_RGB,PAL_AUTO);
+
+ if ( x1 > x2 )
+ {
+ const uint temp = x1;
+ x1 = x2 - 1;
+ x2 = temp + 1;
+ }
+
+ if ( x1 < 0 )
+ x1 = 0;
+ if ( x2 > img->width )
+ x2 = img->width;
+
+ if ( y1 > y2 )
+ {
+ const uint temp = y1;
+ y1 = y2 - 1;
+ y2 = temp + 1;
+ }
+
+ if ( y1 < 0 )
+ y1 = 0;
+ if ( y2 > img->height )
+ y2 = img->height;
+
+ if ( x1 < x2 && y1 < y2 )
+ {
+ Color_t *dest = (Color_t*)img->data + y1 * img->xwidth + x1;
+ const uint skip = img->xwidth - (x2-x1);
+ uint yn = y2 - y1;
+ if ( combine && col.a != 0xff )
+ {
+ const uint r = 0xff * col.a * col.r;
+ const uint g = 0xff * col.a * col.g;
+ const uint b = 0xff * col.a * col.b;
+
+ while ( yn-- > 0 )
+ {
+ uint xn = x2 - x1;
+ while ( xn-- > 0 )
+ {
+ const uint da = ( 0xff - col.a ) * dest->a;
+ dest->r = ( da * dest->r + r ) / 0xfe01;
+ dest->g = ( da * dest->g + g ) / 0xfe01;
+ dest->b = ( da * dest->b + b ) / 0xfe01;
+ dest->a = 0xff - (0xff-dest->a)*(0xff-col.a)/0xff;
+ dest++;
+ }
+ DASSERT( (u8*)dest <= img->data + img->data_size );
+ dest += skip;
+ }
+ }
+ else
+ {
+ while ( yn-- > 0 )
+ {
+ uint xn = x2 - x1;
+ while ( xn-- > 0 )
+ *dest++ = col;
+ DASSERT( (u8*)dest <= img->data + img->data_size );
+ dest += skip;
+ }
+ }
+ }
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError FillHGradientIMG
+(
+ Image_t * img, // image
+ int x1, // left coordinate (robust)
+ int y1, // top coordinate (robust)
+ int x2, // right coordinate (robust)
+ int y2, // bottom coordinate (robust)
+ Color_t col1, // draw color left
+ Color_t col2 // draw color right
+)
+{
+ DASSERT(img);
+ if ( img->iform != IMG_X_RGB )
+ ConvertIMG(img,false,0,IMG_X_RGB,PAL_AUTO);
+
+ if ( x1 > x2 )
+ {
+ const uint temp = x1;
+ x1 = x2 - 1;
+ x2 = temp + 1;
+
+ const Color_t col = col1;
+ col1 = col2;
+ col2 = col;
+ }
+
+ if ( x1 < 0 )
+ x1 = 0;
+ if ( x2 > img->width )
+ x2 = img->width;
+
+ if ( y1 > y2 )
+ {
+ const uint temp = y1;
+ y1 = y2 - 1;
+ y2 = temp + 1;
+ }
+
+ if ( y1 < 0 )
+ y1 = 0;
+ if ( y2 > img->height )
+ y2 = img->height;
+
+ if ( x1 < x2 && y1 < y2 )
+ {
+ Color_t *dest1 = (Color_t*)img->data + y1 * img->xwidth + x1;
+
+ const uint xn = x2 - x1, xn2 = xn/2;
+ uint xi;
+ for ( xi = 0; xi < xn; xi++, dest1++ )
+ {
+ Color_t col;
+ col.r = ( xi * col2.r + (xn-xi) * col1.r + xn2 ) / xn;
+ col.g = ( xi * col2.g + (xn-xi) * col1.g + xn2 ) / xn;
+ col.b = ( xi * col2.b + (xn-xi) * col1.b + xn2 ) / xn;
+ col.a = ( xi * col2.a + (xn-xi) * col1.a + xn2 ) / xn;
+
+ Color_t *dest2 = dest1;
+ uint yn = y2 - y1;
+ while ( yn-- > 0 )
+ {
+ *dest2 = col;
+ dest2 += img->xwidth;
+ }
+ }
+ DASSERT( (u8*)dest1 <= img->data + img->data_size );
+ }
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError FillVGradientIMG
+(
+ Image_t * img, // image
+ int x1, // left coordinate (robust)
+ int y1, // top coordinate (robust)
+ int x2, // right coordinate (robust)
+ int y2, // bottom coordinate (robust)
+ Color_t col1, // draw color top
+ Color_t col2 // draw color bottom
+)
+{
+ DASSERT(img);
+ if ( img->iform != IMG_X_RGB )
+ ConvertIMG(img,false,0,IMG_X_RGB,PAL_AUTO);
+
+ if ( x1 > x2 )
+ {
+ const uint temp = x1;
+ x1 = x2 - 1;
+ x2 = temp + 1;
+ }
+
+ if ( x1 < 0 )
+ x1 = 0;
+ if ( x2 > img->width )
+ x2 = img->width;
+
+ if ( y1 > y2 )
+ {
+ const uint temp = y1;
+ y1 = y2 - 1;
+ y2 = temp + 1;
+
+ const Color_t col = col1;
+ col1 = col2;
+ col2 = col;
+ }
+
+ if ( y1 < 0 )
+ y1 = 0;
+ if ( y2 > img->height )
+ y2 = img->height;
+
+ if ( x1 < x2 && y1 < y2 )
+ {
+ Color_t *dest = (Color_t*)img->data + y1 * img->xwidth + x1;
+ const uint skip = img->xwidth - (x2-x1);
+
+ const uint yn = y2 - y1, yn2 = yn/2;
+ uint yi;
+ for ( yi = 0; yi < yn; yi++ )
+ {
+ Color_t col;
+ col.r = ( yi * col2.r + (yn-yi) * col1.r + yn2 ) / yn;
+ col.g = ( yi * col2.g + (yn-yi) * col1.g + yn2 ) / yn;
+ col.b = ( yi * col2.b + (yn-yi) * col1.b + yn2 ) / yn;
+ col.a = ( yi * col2.a + (yn-yi) * col1.a + yn2 ) / yn;
+
+ uint xn = x2 - x1;
+ while ( xn-- > 0 )
+ *dest++ = col;
+ dest += skip;
+ }
+ DASSERT( (u8*)dest <= img->data + img->data_size );
+ }
+
+ return ERR_OK;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// image resizing ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool fast_resize_enabled = false;
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ResizeIMG
+(
+ Image_t * dest, // dest image (may same as src)
+ bool init_dest, // true: initialize 'dest' first
+ const Image_t * src, // source image (if NULL: use dest)
+ uint width, // new width of image
+ uint height // new height of image
+ // if one of 'width' or 'height' is NULL
+ // -> keep aspect ratio
+)
+{
+ return fast_resize_enabled
+ ? FastResizeIMG(dest,init_dest,src,width,height)
+ : SmartResizeIMG(dest,init_dest,src,width,height);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError FastResizeIMG
+(
+ Image_t * dest, // dest image (may same as src)
+ bool init_dest, // true: initialize 'dest' first
+ const Image_t * src, // source image (if NULL: use dest)
+ uint width, // new width of image
+ uint height // new height of image
+ // if one of 'width' or 'height' is NULL
+ // -> keep aspect ratio
+)
+{
+ DASSERT(dest);
+ if (init_dest)
+ InitializeIMG(dest);
+ if (!src)
+ src = dest;
+
+
+ //--- analyze width and height
+
+ if ( !src->width || !src->height )
+ {
+ CopyIMG(dest,false,src,true);
+ return ERR_OK;
+ }
+
+ if (!width)
+ {
+ if (!height)
+ {
+ CopyIMG(dest,false,src,true);
+ NormalizeFrameIMG(dest);
+ return ERR_OK;
+ }
+ width = ( src->width * height + src->height/2 ) / src->height;
+ }
+ else if (!height)
+ height = ( src->height * width + src->width/2 ) / src->width;
+
+ if ( width == src->width && height == src->height )
+ {
+ //--- no resize needed
+
+ CopyIMG(dest,false,src,true);
+ NormalizeFrameIMG(dest);
+ return ERR_OK;
+ }
+
+ if ( 2*width == src->width && 2*height == src->height )
+ {
+ //--- use fast HalfIMG()
+
+ if ( src != dest )
+ CopyIMG(dest,false,src,true);
+ return HalfIMG(dest);
+ }
+
+
+ //--- convert to IMG_X_RGB
+
+ PRINT("RESIZE: %u*%u -> %u*%u\n",
+ src->width, src->height, width, height );
+
+ if ( src->iform != IMG_X_RGB )
+ {
+ noPRINT("FastResizeIMG: src= %s [%x], dest= %s [%x]\n",
+ PrintFormatIMG(src), src->iform,
+ PrintFormatIMG(dest), dest->iform );
+
+ if (IsGrayIMG(src))
+ {
+ //--- convert temporary to IMG_X_RGB
+
+ enumError err = ConvertIMG(dest,false,src,IMG_X_RGB,PAL_AUTO);
+ noPRINT("FastResizeIMG: gray= %s [%x]\n",PrintFormatIMG(dest),dest->iform);
+ if (!err)
+ {
+ err = FastResizeIMG(dest,false,dest,width,height);
+ if (!err)
+ err = ConvertToGRAY(dest,dest,PAL_AUTO);
+ }
+ return err;
+ }
+
+ enumError err = ConvertIMG(dest,false,src,IMG_X_RGB,PAL_AUTO);
+ if (err)
+ return err;
+ src = dest;
+ noPRINT("FastResizeIMG: conv= %s [%x]\n",
+ PrintFormatIMG(dest), dest->iform );
+
+ if ( src->iform != IMG_X_RGB )
+ return ERROR0(ERR_INTERNAL,
+ "Image format 'X-RGB' expected, but '%s' found\n",
+ PrintFormatIMG(src));
+ }
+
+
+ //--- setup new image data
+
+ DASSERT( src->iform == IMG_X_RGB );
+
+ const uint bytes_per_pixel = 4;
+ const uint xwidth = EXPAND8(width);
+ const uint xheight = EXPAND8(height);
+ const uint data_size = xwidth * xheight * bytes_per_pixel;
+ u8 *data = MALLOC(data_size);
+ memset(data,0xff,data_size);
+ PRINT(" - new image: %u*%u -> %u*%u -> %u bytes\n",
+ width, height, xwidth, xheight, data_size );
+
+ u8 * dest1 = data;
+ const u8 * src1 = src->data;
+ uint src_line_size = bytes_per_pixel * src->xwidth;
+ uint dest_line_size = bytes_per_pixel * xwidth;
+
+
+ //--- main loop
+ // naming scheme: abx: a=Height|Width, b=Src|Dest, x=1|2|N|MAX
+
+ const uint wmax = width < src->width ? width : src->width;
+ const uint hmax = height < src->height ? height : src->height;
+ uint ht, hs2 = 0, hd2 = 0;
+ for ( ht = 1; ht <= hmax; ht++ )
+ {
+ const uint hs1 = hs2;
+ hs2 = ( ht * src->height + hmax/2 ) / hmax;
+ const uint hsn = hs2 - hs1;
+
+ const uint hd1 = hd2;
+ hd2 = ( ht * height + hmax/2 ) / hmax;
+ const uint hdn = hd2 - hd1;
+
+ noPRINT(" LINE: %5u .. %5u -> %5u .. %5u [ %3u -> %3u ]\n",
+ hs1, hs2, hd1, hd2, hsn, hdn );
+ DASSERT( hsn >= 1 && hdn >= 1 );
+ DASSERT( hsn == 1 || hdn == 1 );
+
+ if ( hsn == 1 && width == src->width )
+ {
+ //--- copy complete line without any calculations
+ memcpy(dest1,src1,width*bytes_per_pixel);
+ }
+ else
+ {
+ //--- mix each cell
+
+ u8 * dest2 = dest1;
+ const u8 * src2 = src1;
+
+ uint wd, ws2 = 0, wd2 = 0;
+ for ( wd = 1; wd <= wmax; wd++ )
+ {
+ const uint ws1 = ws2;
+ ws2 = ( wd * src->width + wmax/2 ) / wmax;
+ const uint wsn = ws2 - ws1;
+
+ const uint wd1 = wd2;
+ wd2 = ( wd * width + wmax/2 ) / wmax;
+ const uint wdn = wd2 - wd1;
+
+ noPRINT("COPY: %4u..%4u*%4u..%4u -> %4u..%4u*%4u..%4u [ %2u*%2u -> %2u*%2u ]\n",
+ ws1, ws2, hs1, hs2,
+ wd1, wd2, hd1, hd2,
+ wsn, wdn, hsn, hdn );
+ DASSERT( wsn >= 1 && wdn >= 1 );
+ DASSERT( wsn == 1 || wdn == 1 );
+
+ DASSERT(bytes_per_pixel==4);
+ uint sum[4];
+ memset(sum,0,sizeof(sum));
+
+ if ( hsn == 1 )
+ {
+ uint nw = wsn;
+ while ( nw-- > 0 )
+ {
+ sum[0] += *src2++;
+ sum[1] += *src2++;
+ sum[2] += *src2++;
+ sum[3] += *src2++;
+ }
+ }
+ else if ( hsn == 2 )
+ {
+ uint nw = wsn;
+ while ( nw-- > 0 )
+ {
+ sum[0] += (uint)*src2 + src2[src_line_size]; src2++;
+ sum[1] += (uint)*src2 + src2[src_line_size]; src2++;
+ sum[2] += (uint)*src2 + src2[src_line_size]; src2++;
+ sum[3] += (uint)*src2 + src2[src_line_size]; src2++;
+ }
+ }
+ else
+ {
+ uint nh = hsn;
+ while ( nh-- > 0 )
+ {
+ const u8 * src3 = src2 + nh * src_line_size;
+ uint nw = wsn;
+ while ( nw-- > 0 )
+ {
+ sum[0] += *src3++;
+ sum[1] += *src3++;
+ sum[2] += *src3++;
+ sum[3] += *src3++;
+ }
+ }
+ src2 += wsn * bytes_per_pixel;
+ }
+
+ uint n = wdn, div = hsn * wsn;
+ while ( n-- > 0 )
+ {
+ *dest2++ = sum[0] / div;
+ *dest2++ = sum[1] / div;
+ *dest2++ = sum[2] / div;
+ *dest2++ = sum[3] / div;
+ }
+ }
+ }
+
+ //--- duplicate lines
+
+ uint i;
+ for ( i = 1; i < hdn; i++ )
+ {
+ memcpy(dest1+dest_line_size,dest1,dest_line_size);
+ dest1 += dest_line_size;
+ }
+
+ //--- adjust first level pointers
+
+ dest1 += dest_line_size;
+ src1 += src_line_size * hsn;
+ }
+
+
+ //--- assign data
+
+ FreeIMG(dest,src);
+
+ dest->data = data;
+ dest->data_alloced = true;
+ dest->width = width;
+ dest->height = height;
+ dest->xwidth = xwidth;
+ dest->xheight = xheight;
+ dest->data_size = dest->xwidth * dest->xheight * 4;
+ dest->iform = IMG_X_RGB;
+ NormalizeFrameIMG(dest);
+
+ PRINT(" - new image: %u*%u -> %u*%u -> %u bytes\n",
+ dest->width, dest->height, dest->xwidth, dest->xheight, dest->data_size );
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SmartResizeIMG
+(
+ Image_t * dest, // dest image (may same as src)
+ bool init_dest, // true: initialize 'dest' first
+ const Image_t * src, // source image (if NULL: use dest)
+ uint width, // new width of image
+ uint height // new height of image
+ // if one of 'width' or 'height' is NULL
+ // -> keep aspect ratio
+)
+{
+ DASSERT(dest);
+ if (init_dest)
+ InitializeIMG(dest);
+ if (!src)
+ src = dest;
+
+
+ //--- analyze width and height
+
+ if ( !src->width || !src->height )
+ {
+ CopyIMG(dest,false,src,true);
+ return ERR_OK;
+ }
+
+ if (!width)
+ {
+ if (!height)
+ {
+ CopyIMG(dest,false,src,true);
+ NormalizeFrameIMG(dest);
+ return ERR_OK;
+ }
+ width = ( src->width * height + src->height/2 ) / src->height;
+ }
+ else if (!height)
+ height = ( src->height * width + src->width/2 ) / src->width;
+
+ if ( width == src->width && height == src->height )
+ {
+ //--- no resize needed
+
+ CopyIMG(dest,false,src,true);
+ NormalizeFrameIMG(dest);
+ return ERR_OK;
+ }
+
+ if ( 2*width == src->width && 2*height == src->height )
+ {
+ //--- use fast HalfIMG()
+
+ if ( src != dest )
+ CopyIMG(dest,false,src,true);
+ return HalfIMG(dest);
+ }
+
+
+ //--- convert to IMG_X_RGB
+
+ PRINT("RESIZE: %u*%u -> %u*%u\n",
+ src->width, src->height, width, height );
+
+ if ( src->iform != IMG_X_RGB )
+ {
+ noPRINT("SmartResizeIMG: src= %s [%x], dest= %s [%x]\n",
+ PrintFormatIMG(src), src->iform,
+ PrintFormatIMG(dest), dest->iform );
+
+ if (IsGrayIMG(src))
+ {
+ //--- convert temporary to IMG_X_RGB
+
+ enumError err = ConvertIMG(dest,false,src,IMG_X_RGB,PAL_AUTO);
+ noPRINT("SmartResizeIMG: gray= %s [%x]\n",PrintFormatIMG(dest),dest->iform);
+ if (!err)
+ {
+ err = SmartResizeIMG(dest,false,dest,width,height);
+ if (!err)
+ err = ConvertToGRAY(dest,dest,PAL_AUTO);
+ }
+ return err;
+ }
+
+ enumError err = ConvertIMG(dest,false,src,IMG_X_RGB,PAL_AUTO);
+ if (err)
+ return err;
+ src = dest;
+ noPRINT("SmartResizeIMG: conv= %s [%x]\n",
+ PrintFormatIMG(dest), dest->iform );
+
+ if ( src->iform != IMG_X_RGB )
+ return ERROR0(ERR_INTERNAL,
+ "Image format 'X-RGB' expected, but '%s' found\n",
+ PrintFormatIMG(src));
+ }
+
+
+ //--- setup new image data
+
+ DASSERT( src->iform == IMG_X_RGB );
+
+ const uint bytes_per_pixel = 4;
+ const uint xwidth = EXPAND8(width);
+ const uint xheight = EXPAND8(height);
+ const uint data_size = xwidth * xheight * bytes_per_pixel;
+ u8 *data = MALLOC(data_size);
+ memset(data,0xff,data_size);
+ PRINT(" - new image: %u*%u -> %u*%u -> %u bytes\n",
+ width, height, xwidth, xheight, data_size );
+
+ u8 * dest1 = data;
+ const uint dest_line_skip = bytes_per_pixel * ( xwidth - width );
+
+ //const u8 * src1 = src->data;
+ const uint src_line_size = bytes_per_pixel * src->xwidth;
+ u8 *temp_src = MALLOC(src_line_size);
+ //const uint dest_line_size = bytes_per_pixel * xwidth;
+
+ ResizeHelper_t rh, rv;
+ InitializeResize(&rh,src->width,width);
+ InitializeResize(&rv,src->height,height);
+
+ bool v_ok;
+ for ( v_ok = FirstResize(&rv); v_ok; v_ok = NextResize(&rv) )
+ {
+ //--- proccess vertical points
+
+ const u8 *src2 = src->data + src_line_size * rv.src_idx;
+ u8 *temp = temp_src;
+ uint n = src->width;
+ while ( n-- > 0 )
+ {
+ uint sum0 = rv.half_factor;
+ uint sum1 = rv.half_factor;
+ uint sum2 = rv.half_factor;
+ uint sum3 = rv.half_factor;
+
+ const u8 *src3 = src2;
+ const ResizeElement_t *re;
+ for ( re = rv.elem; re->n_elem; re++ )
+ {
+ uint ne;
+ for ( ne = re->n_elem; ne > 0; ne-- )
+ {
+ noPRINT("COPY %u, %u: %zu / %zu / %u\n",
+ rv.src_idx, ne,
+ ( src2 - src->data ) / src_line_size,
+ ( src3 - src->data ) / src_line_size,
+ src->height );
+ DASSERT( src3 < src->data + src->data_size );
+ sum0 += src3[0] * re->factor;
+ sum1 += src3[1] * re->factor;
+ sum2 += src3[2] * re->factor;
+ sum3 += src3[3] * re->factor;
+ src3 += src_line_size;
+ }
+ }
+ *temp++ = sum0 / rv.sum_factor;
+ *temp++ = sum1 / rv.sum_factor;
+ *temp++ = sum2 / rv.sum_factor;
+ *temp++ = sum3 / rv.sum_factor;
+ DASSERT( temp <= temp_src + src_line_size );
+ src2 += bytes_per_pixel;
+ DASSERT( src2 <= src->data + src->data_size );
+ }
+ DASSERT( temp == temp_src + bytes_per_pixel * src->width );
+
+ #if HAVE_PRINT0
+ {
+ static bool done = false;
+ if (!done)
+ {
+ done = true;
+ HEXDUMP16(4,0,temp_src,temp-temp_src);
+ }
+ }
+ #endif
+
+ bool h_ok;
+ for ( h_ok = FirstResize(&rh); h_ok; h_ok = NextResize(&rh) )
+ {
+ uint sum0 = rh.half_factor;
+ uint sum1 = rh.half_factor;
+ uint sum2 = rh.half_factor;
+ uint sum3 = rh.half_factor;
+
+ u8 *temp = temp_src + rh.src_idx * bytes_per_pixel;
+
+ const ResizeElement_t *re;
+ for ( re = rh.elem; re->n_elem; re++ )
+ {
+ uint ne;
+ for ( ne = re->n_elem; ne > 0; ne-- )
+ {
+ sum0 += *temp++ * re->factor;
+ sum1 += *temp++ * re->factor;
+ sum2 += *temp++ * re->factor;
+ sum3 += *temp++ * re->factor;
+ }
+ }
+
+ *dest1++ = sum0 / rh.sum_factor;
+ *dest1++ = sum1 / rh.sum_factor;
+ *dest1++ = sum2 / rh.sum_factor;
+ *dest1++ = sum3 / rh.sum_factor;
+ }
+ dest1 += dest_line_skip;
+ noPRINT("DEST1: %u/4 = %u, line %u.%u/%u\n",
+ (int)(dest1-data),
+ (int)(dest1-data)/bytes_per_pixel,
+ (int)(dest1-data)/(bytes_per_pixel*xwidth),
+ (int)(dest1-data)%(bytes_per_pixel*xwidth), height ) ;
+ }
+ DASSERT_MSG( dest1 == data + (bytes_per_pixel*xwidth) * height,
+ "%p - %p %zd, dest= %lu, col = %lu.%lu/%u line\n",
+ dest1, data + data_size, dest1 - data - data_size,
+ (dest1-data)/bytes_per_pixel,
+ (dest1-data)/(bytes_per_pixel*xwidth),
+ (dest1-data)%(bytes_per_pixel*xwidth), height ) ;
+ FREE(temp_src);
+
+
+ //--- assign data
+
+ FreeIMG(dest,src);
+
+ dest->data = data;
+ dest->data_alloced = true;
+ dest->width = width;
+ dest->height = height;
+ dest->xwidth = xwidth;
+ dest->xheight = xheight;
+ dest->data_size = dest->xwidth * dest->xheight * 4;
+ dest->iform = IMG_X_RGB;
+ NormalizeFrameIMG(dest);
+
+ PRINT(" - new image: %u*%u -> %u*%u -> %u bytes\n",
+ dest->width, dest->height, dest->xwidth, dest->xheight, dest->data_size );
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError HalfIMG
+(
+ Image_t * img // source and dest
+)
+{
+ DASSERT(img);
+ Transform2XIMG(img);
+ enumError err = ExecTransformIMG(img);
+ const uint newwd = img->width/2;
+ const uint newht = img->height/2;
+ if ( err || !newwd || !newht )
+ return err;
+
+ NormalizeFrameIMG(img);
+ const uint newxwd = EXPAND8(newwd);
+ const uint newxht = EXPAND8(newht);
+ PRINT("HalfIMG() %u*%u -> %u*%u -> %u*%u\n",
+ img->width, img->height, newwd, newht, newxwd, newxht );
+
+ if ( img->iform == IMG_X_GRAY )
+ {
+ const uint pixel_bytes = 2;
+ const uint src_add = pixel_bytes * img->xwidth;
+ const uint dest_add = pixel_bytes * newxwd;
+ const uint data_size = pixel_bytes * newxwd * newxht;
+
+ u8 *data = MALLOC(data_size);
+ u8 *dest1 = data;
+ const u8 *src1 = img->data;
+ uint ih = newht;
+ while ( ih-- > 0 )
+ {
+ u8 *dest2 = dest1;
+ const u8 *src2 = src1;
+ uint iw = newwd;
+ while ( iw-- > 0 )
+ {
+ *dest2++ = ( *src2
+ + src2[pixel_bytes]
+ + src2[src_add]
+ + src2[src_add+pixel_bytes]
+ + 2 ) / 4;
+ src2++;
+ *dest2++ = ( *src2
+ + src2[pixel_bytes]
+ + src2[src_add]
+ + src2[src_add+pixel_bytes]
+ + 2 ) / 4;
+ src2 += pixel_bytes + 1;
+ DASSERT( dest2 <= data + data_size );
+ DASSERT( src2 <= img->data + img->data_size );
+ }
+ noPRINT("%3u: %u*%u %5zx..%5zx/%x -> %u*%u %5zx..%5zx/%x\n",
+ newht - ih - 1,
+ img->width, img->height, src1-img->data, src2-img->data, img->data_size,
+ newwd, newht, dest1-data, dest2-data, data_size );
+
+ dest1 += dest_add;
+ src1 += 2 * src_add;
+ DASSERT( dest1 <= data + data_size );
+ DASSERT( src1 <= img->data + img->data_size );
+ }
+
+ img->width = newwd;
+ img->height= newht;
+ AssignDataGRAY(img,0,data);
+ return ERR_OK;
+ }
+
+ if ( img->iform >= IMG_X_PAL__MIN && img->iform <= IMG_X_PAL__MAX )
+ {
+ enumError err = ConvertToRGB(img,img,PAL_AUTO);
+ if (err)
+ return err;
+ }
+
+ if ( img->iform == IMG_X_RGB )
+ {
+ const uint pixel_bytes = 4;
+ const uint src_add = pixel_bytes * img->xwidth;
+ const uint dest_add = pixel_bytes * newxwd;
+ const uint data_size = pixel_bytes * newxwd * newxht;
+
+ u8 *data = MALLOC(data_size);
+ u8 *dest1 = data;
+ const u8 *src1 = img->data;
+ uint ih = newht;
+ while ( ih-- > 0 )
+ {
+ u8 *dest2 = dest1;
+ const u8 *src2 = src1;
+ uint iw = newwd;
+ while ( iw-- > 0 )
+ {
+ *dest2++ = ( *src2
+ + src2[pixel_bytes]
+ + src2[src_add]
+ + src2[src_add+pixel_bytes]
+ + 2 ) / 4;
+ src2++;
+ *dest2++ = ( *src2
+ + src2[pixel_bytes]
+ + src2[src_add]
+ + src2[src_add+pixel_bytes]
+ + 2 ) / 4;
+ src2++;
+ *dest2++ = ( *src2
+ + src2[pixel_bytes]
+ + src2[src_add]
+ + src2[src_add+pixel_bytes]
+ + 2 ) / 4;
+ src2++;
+ *dest2++ = ( *src2
+ + src2[pixel_bytes]
+ + src2[src_add]
+ + src2[src_add+pixel_bytes]
+ + 2 ) / 4;
+ src2 += pixel_bytes + 1;
+ DASSERT( dest2 <= data + data_size );
+ DASSERT( src2 <= img->data + img->data_size );
+ }
+ noPRINT("%3u: %u*%u %5zx..%5zx/%x -> %u*%u %5zx..%5zx/%x\n",
+ newht - ih - 1,
+ img->width, img->height, src1-img->data, src2-img->data, img->data_size,
+ newwd, newht, dest1-data, dest2-data, data_size );
+
+ dest1 += dest_add;
+ src1 += 2 * src_add;
+ DASSERT( dest1 <= data + data_size );
+ DASSERT( src1 <= img->data + img->data_size );
+ }
+
+ img->width = newwd;
+ img->height = newht;
+ AssignDataRGB(img,0,data);
+ }
+ else
+ return ERROR0(ERR_INTERNAL,0);
+
+ return ERR_OK;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// patching helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static PatchImage_t calc_copy_range
+(
+ PatchRange1_t *dest,
+ PatchRange1_t *src1,
+ PatchRange1_t *src2,
+ PatchImage_t patch_mode,
+ PatchImage_t align_mode
+)
+{
+ DASSERT(dest);
+ DASSERT(src1);
+ DASSERT(src2);
+
+ switch ( patch_mode & PIM_M_SIZE )
+ {
+ case PIM_GROW:
+ dest->size = src1->size > src2->size ? src1->size : src2->size;
+ break;
+
+ case PIM_SHRINK:
+ dest->size = src1->size < src2->size ? src1->size : src2->size;
+ break;
+
+ default: // PIM_LEAVE
+ dest->size = src1->size;
+ break;
+ };
+
+ switch ( align_mode & PIM_M_V )
+ {
+ case PIM_TOP:
+ src1->dest = 0;
+ src1->src = 0;
+ src1->len = dest->size < src1->size ? dest->size : src1->size;
+
+ src2->dest = 0;
+ src2->src = 0;
+ src2->len = dest->size < src2->size ? dest->size : src2->size;
+ break;
+
+ case PIM_BOTTOM:
+ if ( dest->size >= src1->size )
+ {
+ src1->dest = dest->size - src1->size;
+ src1->src = 0;
+ src1->len = src1->size;
+ }
+ else
+ {
+ src1->dest = 0;
+ src1->src = src1->size - dest->size;
+ src1->len = dest->size;
+ }
+
+ if ( dest->size >= src2->size )
+ {
+ src2->dest = dest->size - src2->size;
+ src2->src = 0;
+ src2->len = src2->size;
+ }
+ else
+ {
+ src2->dest = 0;
+ src2->src = src2->size - dest->size;
+ src2->len = dest->size;
+ }
+ break;
+
+ case PIM_INS_TOP:
+ // override the local size and global copy mode!
+ patch_mode = patch_mode & ~PIM_M_COPY | PIM_COPY;
+ dest->size = src1->size + src2->size;
+
+ src1->dest = src2->size;
+ src1->src = 0;
+ src1->len = src1->size;
+
+ src2->dest = 0;
+ src2->src = 0;
+ src2->len = src2->size;
+ break;
+
+ case PIM_INS_BOTTOM:
+ // override the local size and global copy mode!
+ patch_mode = patch_mode & ~PIM_M_COPY | PIM_COPY;
+ dest->size = src1->size + src2->size;
+
+ src1->dest = 0;
+ src1->src = 0;
+ src1->len = src1->size;
+
+ src2->dest = src1->size;
+ src2->src = 0;
+ src2->len = src2->size;
+ break;
+
+ default: // PIM_VCENTER:
+ if ( dest->size >= src1->size )
+ {
+ src1->dest = ( dest->size - src1->size ) / 2;
+ src1->src = 0;
+ src1->len = src1->size;
+ }
+ else
+ {
+ src1->dest = 0;
+ src1->src = ( src1->size - dest->size ) / 2;
+ src1->len = dest->size;
+ }
+
+ if ( dest->size >= src2->size )
+ {
+ src2->dest = ( dest->size - src2->size ) / 2;
+ src2->src = 0;
+ src2->len = src2->size;
+ }
+ else
+ {
+ src2->dest = 0;
+ src2->src = ( src2->size - dest->size ) / 2;
+ src2->len = dest->size;
+ }
+ break;
+ }
+
+ return patch_mode;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// PatchIMG() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError PatchIMG
+(
+ Image_t * dest_img, // destination image, can be one of the sources
+ Image_t * src_img1, // base source
+ Image_t * src_img2, // patch source
+ PatchImage_t patch_mode // patch mode
+)
+{
+ DASSERT(dest_img);
+ DASSERT(src_img1);
+ DASSERT(src_img2);
+
+ if ( src_img1->iform != IMG_X_RGB )
+ ConvertIMG(src_img1,false,0,IMG_X_RGB,PAL_AUTO);
+ if ( src_img2->iform != IMG_X_RGB )
+ ConvertIMG(src_img2,false,0,IMG_X_RGB,PAL_AUTO);
+
+
+ //--- calculate copy range
+
+ PatchRange2_t dest, src1, src2;
+ src1.x.size = src_img1->width;
+ src1.y.size = src_img1->height;
+ src2.x.size = src_img2->width;
+ src2.y.size = src_img2->height;
+
+ patch_mode = calc_copy_range( &dest.x, &src1.x, &src2.x,
+ patch_mode, patch_mode >> PIM_SHIFT_V_H );
+ patch_mode = calc_copy_range( &dest.y, &src1.y, &src2.y,
+ patch_mode, patch_mode );
+
+ noPRINT(" - COPY-X: %3d..%-3d -> %3d..%-3d | %3d..%-3d -> %3d..%-3d | %3d %3d -> %3d\n",
+ src1.x.src, src1.x.src+src1.x.len, src1.x.dest, src1.x.dest+src1.x.len,
+ src2.x.src, src2.x.src+src2.x.len, src2.x.dest, src2.x.dest+src2.x.len,
+ src1.x.size, src2.x.size, dest.x.size );
+ noPRINT(" - COPY-Y: %3d..%-3d -> %3d..%-3d | %3d..%-3d -> %3d..%-3d | %3d %3d -> %3d\n",
+ src1.y.src, src1.y.src+src1.y.len, src1.y.dest, src1.y.dest+src1.y.len,
+ src2.y.src, src2.y.src+src2.y.len, src2.y.dest, src2.y.dest+src2.y.len,
+ src1.y.size, src2.y.size, dest.y.size );
+
+
+ //--- create the new image
+
+ if ( dest_img == src_img1
+ && dest.x.size == src1.x.size
+ && dest.y.size == src1.y.size )
+ {
+ //--- one direct operation
+
+ PRINT("DIRECT IMAGE OPERATION\n");
+ CopyRectIMG( dest_img, src2.x.dest, src2.y.dest,
+ src_img2, src2.x.src, src2.y.src,
+ src2.x.len, src2.y.len,
+ patch_mode );
+ }
+ else
+ {
+ //--- we need a temporary image
+
+ Color_t transparent;
+ SETCOLOR(transparent,0xff,0xff,0xff,0x00);
+ Image_t temp_img;
+ CreateIMG(&temp_img,true,dest.x.size,dest.y.size,transparent);
+
+ CopyRectIMG( &temp_img, src1.x.dest, src1.y.dest,
+ src_img1, src1.x.src, src1.y.src,
+ src1.x.len, src1.y.len,
+ PIM_COPY );
+
+ CopyRectIMG( &temp_img, src2.x.dest, src2.y.dest,
+ src_img2, src2.x.src, src2.y.src,
+ src2.x.len, src2.y.len,
+ patch_mode );
+
+ MoveDataIMG(dest_img,&temp_img);
+ }
+ return ERR_OK;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// patch interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static bool pimg_valid = false;
+static uint n_pimg = 0;
+static Image_t *pimg = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SetupPatchListIMG()
+{
+ static bool active = false;
+ if ( active || !patch_image_list.used )
+ return false;
+
+ if (pimg_valid)
+ return n_pimg > 0;
+ pimg_valid = active = true;
+
+ TRACE("-\n");
+ TRACE_SIZEOF(PatchRange1_t);
+ TRACE_SIZEOF(PatchRange2_t);
+ TRACE("-\n");
+
+ DASSERT(!pimg);
+ pimg = CALLOC(patch_image_list.used,sizeof(*pimg));
+ n_pimg = 0;
+
+ uint ip;
+ for ( ip = 0; ip < patch_image_list.used; ip++ )
+ {
+ const FormatFieldItem_t * ffi = patch_image_list.list + ip;
+ Image_t *img = pimg + n_pimg;
+ if (LoadIMG(img,true,ffi->key,0,false,true,false))
+ ResetIMG(img);
+ else
+ {
+ n_pimg++;
+ img->patch_mode = ffi->patch_mode;
+ ConvertIMG(img,false,0,IMG_X_RGB,PAL_AUTO);
+ PRINT("PATCH-IMG LOADED: %04x = %s\n",img->patch_mode,img->path);
+ }
+ }
+
+ active = false;
+ return n_pimg > 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetPatchListIMG()
+{
+ uint ip;
+ for ( ip = 0; ip < n_pimg; ip++ )
+ ResetIMG( pimg + ip );
+ FREE(pimg);
+
+ pimg_valid = false;
+ n_pimg = 0;
+ pimg = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError PatchListIMG
+(
+ Image_t * img // image to transform
+)
+{
+ DASSERT(img);
+ if ( img->patch_done || !SetupPatchListIMG() )
+ return ERR_OK;
+
+ FreeMipmapsIMG(img);
+ Transform2XIMG(img);
+ enumError err = ExecTransformIMG(img);
+ if ( err || img->iform != IMG_X_RGB )
+ return err;
+
+ PRINT("PATCH %s\n",img->path);
+
+ uint ip;
+ for ( ip = 0; ip < n_pimg && err == ERR_OK; ip++ )
+ {
+ Image_t *pat = pimg + ip;
+ if ( verbose > 1 )
+ fprintf(stdlog," - PATCH %04x = %s\n",pat->patch_mode,pat->path);
+ err = PatchIMG(img,img,pat,pat->patch_mode);
+ }
+
+ img->patch_done = true;
+ return err;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// image lists ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintImageHead
+(
+ int line_indent, // indention of complete line
+ uint long_count // set the verbosity
+)
+{
+ if ( long_count > 1 )
+ printf( "\n"
+ "%*sfile image mip width image attributes(Gray,Alpha,Palette)\n"
+ "%*stype type map height size ^ block file path\n"
+ "%*s%.79s\n",
+ line_indent,"", line_indent,"", line_indent,"", Minus300 );
+ else if ( long_count )
+ printf( "\n"
+ "%*sfile image mip width image\n"
+ "%*stype type map height size file path\n"
+ "%*s%.79s\n",
+ line_indent,"", line_indent,"", line_indent,"", Minus300 );
+ else
+ printf( "\n"
+ "%*sfile image mip\n"
+ "%*stype type map file path\n"
+ "%*s%.79s\n",
+ line_indent,"", line_indent,"", line_indent,"", Minus300 );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static ccp SizeColor ( u32 size )
+{
+ if (!size)
+ return colset->warn;
+
+ u32 m = 1;
+ while ( !( m & size ) )
+ m <<= 1;
+
+ return m != size
+ ? colset->warn
+ : m > 1024
+ ? colset->hint
+ : "";
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int PrintImage
+(
+ const void * data, // valid data pointer
+ // if NULL: force data line
+ uint data_size, // size of 'data'
+ ccp fname, // file name for listing
+ int line_indent, // indention of complete line
+ int fname_indent, // indention of 'fname'
+ uint long_count, // set the verbosity
+ bool ignore // true: ignore non images
+)
+{
+ DASSERT(fname);
+
+ Image_t img;
+ InitializeIMG(&img);
+ const ImageGeometry_t *geo = 0;
+
+ if ( data && data_size )
+ {
+ ScanDataIMG(&img,true,data,data_size);
+ geo = GetImageGeometry(img.iform);
+
+ if ( img.info_fform == FF_PNG && long_count )
+ LoadPNG(&img,false,0,fname,0);
+ }
+ else
+ ignore = false;
+
+ if (geo)
+ {
+ printf("%-6s %02x %-6s",
+ GetNameFF(0,img.info_fform),
+ img.iform, GetImageFormatName(img.iform,"?") );
+
+ if ( img.info_n_image < 1 || img.info_n_image == 1 && ignore )
+ fputs(" -",stdout);
+ else
+ printf(" %2u",img.info_n_image-1);
+
+ if (long_count)
+ {
+ ccp wcol, hcol;
+ if (colorize_stdout)
+ {
+ wcol = SizeColor(img.width);
+ hcol = SizeColor(img.height);
+ }
+ else
+ wcol = hcol = "";
+
+ printf(" %s%4u%s*%s%-4u%s %7u",
+ wcol, img.width, *wcol ? TermTextModeReset : "",
+ hcol, img.height, *hcol ? TermTextModeReset : "",
+ img.xwidth * img.xheight * geo->bits_per_pixel / 8 );
+ }
+
+ if (long_count>1)
+ printf(" %c%c%c %u*%u",
+ geo->is_gray ? 'G' :'-',
+ geo->has_alpha ? 'A' :'-',
+ geo->max_palette ? 'P' :'-',
+ geo->block_width, geo->block_height );
+
+ printf(" %*s%s\n",fname_indent,"",fname);
+ }
+ else if ( !ignore || img.width && img.height )
+ {
+ printf("%-6s - - -", GetNameFF(0,img.info_fform) );
+ if (long_count)
+ {
+ if ( img.width && img.height )
+ printf(" %4u*%-4u -",img.width,img.height);
+ else
+ fputs(" - -",stdout);
+ }
+ if (long_count>1)
+ fputs(" --- - ",stdout);
+ printf(" %*s%s\n", fname_indent,"", fname );
+ }
+
+ ResetIMG(&img);
+ return 0;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Median Cut: data structures ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#undef CHAN
+#define CHAN 4
+
+#if 0
+ #define mcPRINT PRINT
+#else
+ #define mcPRINT noPRINT
+#endif
+
+//-----------------------------------------------------------------------------
+
+typedef struct mc_elem_t
+{
+ u8 val[CHAN]; // channel tuple
+ u32 index; // index of element
+
+} mc_elem_t;
+
+//-----------------------------------------------------------------------------
+
+typedef struct mc_block_t
+{
+ u8 min[CHAN]; // minimum value of each channel
+ u8 max[CHAN]; // maximum value of each channel
+ uint max_len; // maximum length of all channels
+
+ mc_elem_t * elem_beg; // pointer to first element
+ mc_elem_t * elem_end; // pointer to end of element list
+
+ struct mc_block_t * next; // pointer to next block
+
+} mc_block_t;
+
+//-----------------------------------------------------------------------------
+
+typedef struct mc_t
+{
+ uint n_elem; // number of element
+ mc_elem_t * elem; // list with all elements, alloced
+
+ uint n_block; // number of used blocks
+ uint max_block; // max number of blocks
+ mc_block_t * block_head; // head of sorted list of blocks
+ mc_block_t * block_data; // alloced blocked data
+ mc_block_t last_block; // special last block
+
+} mc_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Median Cut: Debugging ///////////////
+///////////////////////////////////////////////////////////////////////////////
+#if HAVE_PRINT
+///////////////////////////////////////////////////////////////////////////////
+
+static void DumpBlock ( const mc_t *mc, const mc_block_t *blk, ccp title )
+{
+ DASSERT(mc);
+ DASSERT(blk);
+ DASSERT(CHAN==4);
+
+ mcPRINT("BLOCK %u/%u/%u [%s]\n",
+ (int)( blk - mc->block_data ), mc->n_block, mc->max_block,
+ title ? title : "" );
+
+ mcPRINT(" ** range: %02x..%02x %02x..%02x %02x..%02x %02x..%02x"
+ " ** len: %3u=0x%03x ** elem: %u..%u %3u/%u **\n",
+ blk->min[0], blk->max[0],
+ blk->min[1], blk->max[1],
+ blk->min[2], blk->max[2],
+ blk->min[3], blk->max[3],
+ blk->max_len, blk->max_len,
+ (int)( blk->elem_beg - mc->elem ),
+ (int)( blk->elem_end - mc->elem ),
+ (int)( blk->elem_end - blk->elem_beg ),
+ mc->n_elem );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void DumpBlockList ( const mc_t *mc, ccp title )
+{
+ DASSERT(mc);
+
+ mcPRINT("BLOCK-LIST %u/%u [%s]\n",
+ mc->n_block, mc->max_block, title ? title : "" );
+
+ mc_block_t * blk = mc->block_head;
+ uint cnt;
+ for ( cnt = 0; blk; cnt++, blk = blk->next )
+ {
+//DEL char buf[20];
+//DEL ccp info = "LAST";
+//DEL if ( blk != &mc->last_block )
+//DEL {
+//DEL snprintf(buf,sizeof(buf),"%4u",(int)(blk - mc->block_data));
+//DEL info = buf;
+//DEL }
+
+ mcPRINT("%4u %s: len: %3u=0x%03x ** elem: %u..%u %3u/%u\n",
+ cnt, info, blk->max_len, blk->max_len,
+ (int)( blk->elem_beg - mc->elem ),
+ (int)( blk->elem_end - mc->elem ),
+ (int)( blk->elem_end - blk->elem_beg ),
+ mc->n_elem );
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define DUMPBLOCK(m,b,t) DumpBlock(m,b,t)
+#define DUMPBLOCKLIST(m,t) DumpBlockList(m,t)
+
+#else // !HAVE_PRINT
+ #define DUMPBLOCK(m,b,t)
+ #define DUMPBLOCKLIST(m,t)
+#endif
+
+#define noDUMPBLOCK(m,b,t)
+#define noDUMPBLOCKLIST(m,t)
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Median Cut: Helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static uint CalcMinMax ( mc_block_t *blk )
+{
+ DASSERT(blk);
+ memset(blk->min,~0,sizeof(blk->min));
+ memset(blk->max, 0,sizeof(blk->max));
+
+ const mc_elem_t *elem;
+ for ( elem = blk->elem_beg; elem < blk->elem_end; elem++ )
+ {
+ uint c;
+ for ( c = 0; c < CHAN; c++ )
+ {
+ const u8 val = elem->val[c];
+ noPRINT("C: %02x -> %02x..%02x\n", val, blk->min[c], blk->max[c] );
+ if ( blk->min[c] > val )
+ blk->min[c] = val;
+ if ( blk->max[c] < val )
+ blk->max[c] = val;
+ }
+ }
+
+ uint max_len = 0, c;
+ for ( c = 0; c < CHAN; c++ )
+ {
+ const uint ml = blk->max[c] - blk->min[c];
+ if ( max_len < ml )
+ max_len = ml;
+ }
+
+ return blk->max_len = max_len;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Median Cut: Interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint MedianCut
+(
+ const u32 * data, // input: 4-tuple aka RGBA
+ // data is read at the very beginning
+ // and may overlay with 'index' and/or 'pal'
+ u16 * index, // not NULL: store index into palette,
+ // can be the space as 'data'
+
+ // The follwing 3 parameters are used for 'data' and 'index'.
+ // They allow to skip unused pixel at the end or each row.
+ uint width, // used pixels per row
+ uint xwidth, // >width: pixels per row
+ uint height, // number or rows
+
+ // pallette data
+ u32 * pal, // not NULL: store calculated palette
+ uint pal_elem // number of wanted palette entries
+
+ // return value = number of calculated palette values
+)
+{
+ DASSERT(data);
+ DASSERT(width);
+ DASSERT(height);
+ DASSERT(pal);
+ DASSERT(pal_elem);
+
+ PRINT("MedianCut(), %d,%dx%d, pal=%d, n_pal=%u\n",
+ width, xwidth, height, pal!=0, pal_elem );
+
+ #if HAVE_PRINT || DEBUG
+ u64 usec = GetTimerUSec();
+ #endif
+
+ static bool done = false;
+ if (!done)
+ {
+ done = true;
+ TRACE("- median cut\n");
+ TRACE_SIZEOF(mc_t);
+ TRACE_SIZEOF(mc_block_t);
+ TRACE_SIZEOF(mc_elem_t);
+ TRACE("-\n");
+ }
+
+
+ //--- setup data structure
+
+ const uint n_elem = width * height;
+ if ( xwidth <= width )
+ {
+ // normalization and a little optimization
+ width = xwidth = n_elem;
+ height = 1;
+ }
+
+ if ( pal_elem > 0x10000 )
+ pal_elem = 0x10000;
+
+ mc_t mc;
+ memset(&mc,0,sizeof(mc));
+ mc.n_elem = n_elem;
+ mc.elem = CALLOC(sizeof(*mc.elem),n_elem);
+ mc.n_block = 1;
+ mc.max_block = pal_elem;
+ mc.block_data = CALLOC(sizeof(*mc.block_data),pal_elem);
+ mc.block_head = mc.block_data;
+ mc.last_block.elem_beg = mc.elem;
+ mc.last_block.elem_end = mc.elem;
+
+ //--- setup first block
+
+ {
+ mc_block_t * block = mc.block_head;
+ block->next = &mc.last_block;
+ block->elem_beg = mc.elem;
+ block->elem_end = mc.elem + n_elem;
+
+ const uint delta = xwidth - width;
+ mc_elem_t * elem = mc.elem;
+ uint h = height, i = 0;
+ while ( h-- > 0 )
+ {
+ uint w = width;
+ while ( w-- > 0 )
+ {
+ elem->index = i++;
+ memcpy(elem->val,data,sizeof(elem->val));
+ elem++;
+ data++;
+ }
+ data += delta;
+ i += delta;
+ }
+ CalcMinMax(block);
+ DUMPBLOCK(&mc,block,"startup");
+ }
+
+
+ //--- median cut
+
+ while ( mc.n_block < mc.max_block )
+ {
+ //--- pick first block
+
+ mc_block_t * b1 = mc.block_head;
+ DASSERT( b1 );
+ DASSERT( b1->next );
+ DASSERT( b1->max_len >= b1->next->max_len );
+
+ if (!b1->max_len)
+ break;
+
+ mc.block_head = b1->next;
+
+
+ //--- find channel with max length
+
+ uint c;
+ for ( c = 0; b1->max[c] - b1->min[c] != b1->max_len; c++ )
+ ;
+ DASSERT( c < CHAN );
+
+
+ //--- split block
+
+ DASSERT( mc.n_block < mc.max_block );
+ mc_block_t *b2 = mc.block_data + mc.n_block++;
+ memcpy(b2,b1,sizeof(*b2));
+
+ const u8 split_val = ( b1->min[c] + b1->max[c] + 1 ) / 2;
+ mcPRINT("\n----- SPLIT b=%zu,%zu, c#%u at %02x, len=%02x -----\n",
+ b1 - mc.block_data, b2 - mc.block_data,
+ c, split_val, b1->max_len );
+ //b1->max[c] = b1->min[c];
+ //b2->min[c] = b2->max[c];
+
+ mc_elem_t * e1 = b1->elem_beg;
+ mc_elem_t * e2 = b1->elem_end - 1;
+ for(;;)
+ {
+ //--- find elements to swap
+
+ while ( e1 < e2 && e1->val[c] < split_val )
+ e1++;
+
+ while ( e1 < e2 && e2->val[c] >= split_val )
+ e2--;
+
+ if ( e1 >= e2 )
+ break;
+
+ //--- swap elements
+
+ noPRINT("SWAP: %zu,%zu : #%u %02x >= %02x > %02x\n",
+ e1-mc.elem,e2-mc.elem,
+ c, e1->val[c], split_val, e2->val[c] );
+ mc_elem_t temp;
+ memcpy(&temp,e1,sizeof(temp));
+ memcpy(e1,e2,sizeof(temp));
+ memcpy(e2,&temp,sizeof(temp));
+ }
+ mcPRINT(" %zu,%zu,%zu,%zu : #%u %02x < %02x <= %02x\n",
+ b1->elem_beg - mc.elem, e1 - mc.elem,
+ e2 - mc.elem, b1->elem_end - mc.elem,
+ c, e1[-1].val[c], split_val, e1[0].val[c] );
+ DASSERT( e1 > b1->elem_beg );
+ DASSERT( e1 < b1->elem_end );
+ DASSERT ( e1[-1].val[c] < split_val );
+ DASSERT ( e1[ 0].val[c] >= split_val );
+
+ b1->elem_end = b2->elem_beg = e1;
+ CalcMinMax(b1);
+ CalcMinMax(b2);
+
+ DUMPBLOCK(&mc,b1,"split 1");
+ DUMPBLOCK(&mc,b2,"split 2");
+
+
+ //--- insert both blocks into list
+
+ noDUMPBLOCKLIST(&mc,"0");
+
+ if ( b1->max_len < b2->max_len )
+ {
+ mc_block_t *temp = b1;
+ b1 = b2;
+ b2 = temp;
+ }
+
+ mc_block_t **ptr = &mc.block_head;
+ while ( (*ptr)->max_len > b1->max_len )
+ ptr = &(*ptr)->next;
+ b1->next = *ptr;
+ *ptr = b1;
+ noDUMPBLOCKLIST(&mc,"b1");
+
+ while ( (*ptr)->max_len > b2->max_len )
+ ptr = &(*ptr)->next;
+ b2->next = *ptr;
+ *ptr = b2;
+ noDUMPBLOCKLIST(&mc,"b2");
+ }
+ DUMPBLOCKLIST(&mc,"END");
+
+
+ //--- fill index table
+
+ if (index)
+ {
+ const uint max_index = xwidth * height;
+ memset(index,0,max_index*sizeof(*index));
+ uint blk_idx = 0;
+ const mc_block_t * blk;
+ for ( blk = mc.block_head; blk != &mc.last_block; blk = blk->next, blk_idx++ )
+ {
+ const mc_elem_t * elem;
+ for ( elem = blk->elem_beg; elem < blk->elem_end; elem++ )
+ {
+ DASSERT( elem->index < max_index );
+ index[elem->index] = blk_idx;
+ }
+ }
+ }
+
+
+ //--- fill palette table
+
+ if (pal)
+ {
+ u8 *dest = (u8*)pal;
+ const mc_block_t * blk;
+ for ( blk = mc.block_head; blk != &mc.last_block; blk = blk->next )
+ {
+ uint val[CHAN];
+ memset(val,0,sizeof(val));
+
+ const mc_elem_t * elem;
+ for ( elem = blk->elem_beg; elem < blk->elem_end; elem++ )
+ {
+ uint c;
+ for ( c = 0; c < CHAN; c++ )
+ val[c] += elem->val[c];
+ }
+
+ const uint n = blk->elem_end - blk->elem_beg;
+ DASSERT(n);
+ uint c;
+ for ( c = 0; c < CHAN; c++ )
+ *dest++ = ( val[c] + n/2 ) / n;
+ }
+ }
+
+
+ //--- clean & return
+
+ FREE(mc.elem);
+ FREE(mc.block_data);
+
+
+ #if HAVE_PRINT
+ usec = GetTimerUSec() - usec;
+ PRINT("MEDIAN-CUT: elem=%u, pal=%u/%u, %llu µsec\n",
+ n_elem, mc.n_block, pal_elem, usec );
+ #elif DEBUG
+ usec = GetTimerUSec() - usec;
+ TRACE("MEDIAN-CUT: elem=%u, pal=%u/%u, %llu µsec\n",
+ n_elem, mc.n_block, pal_elem, usec );
+ #else
+ PRINT("MEDIAN-CUT: elem=%u, pal=%u/%u\n", n_elem, mc.n_block, pal_elem );
+ #endif
+
+ return mc.n_block;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+