From 60ccf14d12b8cb511311cafa124c555dc17c45fb Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Fri, 13 Sep 2019 12:15:20 -0700 Subject: [PATCH] Edit colors on images with adaptive palettes. In Zork Zero and Arthur, some images have no palettes of their own. These change their colors according to images plotted before. Kevin Bracy called these "adaptive palette pictures". To deal with them, he proposed an extension to the Blorb standard to add an "APal" chunk which lists the images that have adaptive palettes. The changes in this commit make sfrotz read that chunk and then rewrite the palettes of listed images when they are loaded. This is described in much greater detail in http://ifarchive.org/if-archive/programming/blorb/blorb-infocom-extension.txt The work to correctly implement processing of adaptive palettes was done by Paul Gilbert . I, David Griffith, condensed what would have been an irritatingly messy merge into a single commit and did general cleanup before committing Paul's changes using his name. --- src/sdl/sf_frotz.h | 6 +++ src/sdl/sf_images.c | 112 +++++++++++++++++------------------------- src/sdl/sf_resource.c | 23 ++++++++- src/sdl/sf_video.c | 83 ++++++++++++++++++++++++------- 4 files changed, 138 insertions(+), 86 deletions(-) diff --git a/src/sdl/sf_frotz.h b/src/sdl/sf_frotz.h index 8561223..c2161ed 100644 --- a/src/sdl/sf_frotz.h +++ b/src/sdl/sf_frotz.h @@ -21,6 +21,7 @@ typedef struct { int sf_getresource( int num, int ispic, int method, myresource * res); void sf_freeresource( myresource *res); +bool sf_IsAdaptive(int picture); #ifndef true #define true 1 @@ -104,6 +105,11 @@ typedef struct { int number; // 0 means unallocated int width, height; byte *pixels; + ulong palette[16]; + int palette_entries; + int transparentcolor; + bool adaptive; + bool usespalette; } sf_picture; #define DEFAULT_GAMMA 2.2 diff --git a/src/sdl/sf_images.c b/src/sdl/sf_images.c index 787bd94..f577b1d 100644 --- a/src/sdl/sf_images.c +++ b/src/sdl/sf_images.c @@ -65,7 +65,7 @@ static int loadpng( byte *data, int length, sf_picture *graphic) png_infop end_info = NULL; PNGData pngData; png_uint_32 width, height; - int i, bit_depth, color_type, size; + int color_type, size; double gamma; graphic->pixels = NULL; @@ -115,64 +115,55 @@ static int loadpng( byte *data, int length, sf_picture *graphic) width = png_get_image_width(png_ptr,info_ptr); height = png_get_image_height(png_ptr,info_ptr); - bit_depth = png_get_bit_depth(png_ptr,info_ptr); color_type = png_get_color_type(png_ptr,info_ptr); graphic->width = width; graphic->height = height; - - if (color_type == PNG_COLOR_TYPE_PALETTE && bit_depth <= 8) - png_set_palette_to_rgb(png_ptr); - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) - png_set_expand_gray_1_2_4_to_8(png_ptr); - if (png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS)) - png_set_tRNS_to_alpha(png_ptr); + graphic->usespalette = FALSE; if (png_get_gAMA(png_ptr,info_ptr,&gamma)) png_set_gamma(png_ptr,m_gamma,gamma); - if (bit_depth == 16) - png_set_strip_16(png_ptr); - if (bit_depth < 8) - png_set_packing(png_ptr); - if (color_type == PNG_COLOR_TYPE_GRAY || - color_type == PNG_COLOR_TYPE_GRAY_ALPHA) - png_set_gray_to_rgb(png_ptr); - - // png_set_bgr(png_ptr); - png_set_filler(png_ptr,0xFF,PNG_FILLER_AFTER); - -// graphic->m_header = new BITMAPINFOHEADER; -// ::ZeroMemory(graphic->m_header,sizeof(BITMAPINFOHEADER)); -// graphic->m_header->biSize = sizeof(BITMAPINFOHEADER); -// graphic->m_header->biWidth = width; -// graphic->m_header->biHeight = height*-1; -// graphic->m_header->biPlanes = 1; -// graphic->m_header->biBitCount = 32; -// graphic->m_header->biCompression = BI_RGB; - - size = width*height*4; - graphic->pixels = (byte *)malloc(size); + if (color_type == PNG_COLOR_TYPE_PALETTE) { + graphic->usespalette = TRUE; + png_set_packing(png_ptr); + + // Check for transparency. In practice, the transparent + // color will always be color 0. + png_bytep trans; + int num_trans; + png_color_16p trans_values; + + if (png_get_tRNS(png_ptr,info_ptr,&trans,&num_trans,&trans_values) && num_trans >= 1) + graphic->transparentcolor = trans[0]; + + size = width*height; + graphic->pixels = (byte *)malloc(size); + + rowPointers = malloc(sizeof(png_bytep) * height); + for (int i = 0; i < (int)height; i++) + rowPointers[i] = graphic->pixels+(width*i); + png_read_image(png_ptr,rowPointers); + + // Get the palette after reading the image, so that the gamma + // correction is applied. + png_colorp palette; + int num_palette; + if (png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette)) { + graphic->palette_entries = num_palette; + for (int i = 0; i < num_palette; i++) { + ulong color = palette[i].red|(palette[i].green<<8)|(palette[i].blue<<16); + graphic->palette[i] = color; + } + } + } else { + if (graphic->adaptive) + os_fatal("Non-paletted graphics cannot be adaptive"); + + os_fatal("TODO: Support loading of non-paletted images"); + } - rowPointers = (png_bytep *) malloc(height*sizeof(png_bytep)); - for (i = 0; i < (int)height; i++) - rowPointers[i] = graphic->pixels+(width*i*4); - png_read_image(png_ptr,rowPointers); - - // Get the palette after reading the image, so that the gamma - // correction is applied -// png_colorp palette; -// int num_palette; -// if (png_get_PLTE(png_ptr,info_ptr,&palette,&num_palette)) -// { -// for (int i = 0; i < num_palette; i++) -// { -// DWORD colour = -// (palette[i].red<<16)|(palette[i].green<<8)|palette[i].blue; -// graphic->m_palette.Add(colour); -// graphic->m_invPalette[colour] = i; -// } -// } + /* Reading done. */ png_read_end(png_ptr,end_info); png_destroy_read_struct(&png_ptr,&info_ptr,&end_info); @@ -280,16 +271,6 @@ static int loadjpeg( byte *data, int length, sf_picture *graphic) graphic->height = height; size = width*height*4; graphic->pixels = (byte *)malloc(size); - -// graphic->m_header = new BITMAPINFOHEADER; -// ::ZeroMemory(graphic->m_header,sizeof(BITMAPINFOHEADER)); -// graphic->m_header->biSize = sizeof(BITMAPINFOHEADER); -// graphic->m_header->biWidth = width; -// graphic->m_header->biHeight = height*-1; -// graphic->m_header->biPlanes = 1; -// graphic->m_header->biBitCount = 32; -// graphic->m_header->biCompression = BI_RGB; -// graphic->m_pixels = new BYTE[width*height*4]; // Force RGB output info.out_color_space = JCS_RGB; @@ -308,9 +289,6 @@ static int loadjpeg( byte *data, int length, sf_picture *graphic) (width*(info.output_scanline-1)*4); for (i = 0; i < width; i++) { -/* pixelRow[(i*4)+0] = (*buffer)[(i*3)+2]; - pixelRow[(i*4)+1] = (*buffer)[(i*3)+1]; - pixelRow[(i*4)+2] = (*buffer)[(i*3)+0];*/ pixelRow[(i*4)+0] = (*buffer)[(i*3)+0]; pixelRow[(i*4)+1] = (*buffer)[(i*3)+1]; pixelRow[(i*4)+2] = (*buffer)[(i*3)+2]; @@ -343,6 +321,9 @@ static int sf_loadpic( int picture, sf_picture *graphic) myresource res; int st = 0; + // Set whether graphic has an adaptive palette + graphic->adaptive = sf_IsAdaptive(picture) ? TRUE : FALSE; + if (sf_getresource( picture, 1, bb_method_Memory,&res) == bb_err_None) { byte * data = (byte *)res.bbres.data.ptr; @@ -363,11 +344,11 @@ static int sf_loadpic( int picture, sf_picture *graphic) else if (id == bb_ID_Rect) st = loadrect( data, length, graphic); sf_freeresource(&res); - } + } if (st) graphic->number = picture; return st; - } +} //////////////////// // CACHE @@ -425,4 +406,3 @@ sf_picture * sf_getpic( int num){ if (sf_loadpic( num, res)) return res; return NULL; } - diff --git a/src/sdl/sf_resource.c b/src/sdl/sf_resource.c index 10f8c50..a59d13c 100644 --- a/src/sdl/sf_resource.c +++ b/src/sdl/sf_resource.c @@ -500,7 +500,7 @@ void sf_readsettings(void) m_fixedFontName = sf_GetProfileString("Display","Fixed Font Name", "Courier New"); m_fontSize = sf_GetProfileInt("Display","Font Size",10);*/ - + m_v6scale = sf_GetProfileInt("Display","Infocom V6 Scaling",2); m_gfxScale = 1; m_defaultFore = ( sf_GetProfileInt("Display","Foreground",0xffffff)); @@ -849,6 +849,27 @@ bool sf_IsInfocomV6() return false; } +// If true, this picture has an adaptive palette +bool sf_IsAdaptive(int picture) +{ + bb_result_t result; + bool adaptive = FALSE; + + if (bb_load_chunk_by_type(bmap, bb_method_Memory, &result, bb_ID_APal, 0) == bb_err_None) { + for (int i = 0; i < (int) result.length; i += 4) { + unsigned char* data = ((unsigned char*) result.data.ptr)+i; + int entry = (data[0]<<24)|(data[1]<<16)|(data[2]<<8)|data[3]; + if (picture == entry) { + adaptive = TRUE; + break; + } + } + } + bb_unload_chunk(bmap, result.chunknum); + return adaptive; +} + + #define LOCAL_MEM -1 #define LOCAL_FILE -2 diff --git a/src/sdl/sf_video.c b/src/sdl/sf_video.c index 533f6b2..565ad76 100644 --- a/src/sdl/sf_video.c +++ b/src/sdl/sf_video.c @@ -26,6 +26,9 @@ bool sdl_active; static void sf_quitconf(); +static bool ApplyPalette(sf_picture *); +static ulong screen_palette[16]; + // clipping region static int xmin,xmax,ymin,ymax; @@ -532,29 +535,47 @@ void os_draw_picture(int picture, int y, int x) if (ew <= 0) return; if (eh <= 0) return; - for (yy=0;yy> 24); - if (alpha == 255) - dval = sval & 0xffffff; - else - dval = sf_blend((int)(alpha + (alpha>>7)),sval,dst[0]); - for (iy=0;iywidth; + /* Use simple scaling without interpolation in palette mode. */ + /* Adapted from start of FrotzGfx::Paint() in Windows Frotz. */ + if (pic->usespalette) { + if (!pic->adaptive && ApplyPalette(pic)) + sf_flushdisplay(); + + for (yy=0; yy < eh * m_gfxScale; yy++) { + int ys = yy / m_gfxScale; + for (xx = 0; xx < ew * m_gfxScale; xx++) { + int xs = xx / m_gfxScale; + int index = pic->pixels[ys * pic->width + xs]; + if (index != pic->transparentcolor) + sf_wpixel(x + xx, y + yy, screen_palette[index]); + } } + } else { + if (pic->adaptive) + os_fatal("Adaptive images must be paletted"); + + for (yy=0; yy < eh; yy++) { + for (xx = 0; xx < ew; xx++) { + dst = sbuffer + x +xx*m_gfxScale + sbpitch*(y + yy*m_gfxScale); + sval = src[xx]; + alpha = (sval >> 24); + if (alpha == 255) + dval = sval & 0xffffff; + else + dval = sf_blend((int)(alpha + (alpha>>7)),sval,dst[0]); + for (iy=0;iywidth; + } } + dirty = 1; +} + static ulong mytimeout; int mouse_button; static int numAltQ = 0; @@ -1183,3 +1204,27 @@ void os_tick() { } } } + + +/* Apply the picture's palette to the screen palette. */ +/* Adapted from FrotzGfx::ApplyPalette() in Windows Frotz. */ +static bool ApplyPalette(sf_picture *graphic) +{ + bool changed = FALSE; + int i, colors; + + memset(&screen_palette, 0, sizeof(ulong)); + + if (graphic->usespalette) { + colors = graphic->palette_entries; + if (colors > 16) + colors = 16; + for (i = 0; i < colors; i++) { + if (screen_palette[i] != graphic->palette[i]) { + changed = TRUE; + screen_palette[i] = graphic->palette[i]; + } + } + } + return changed; +} -- 2.34.1