Edit colors on images with adaptive palettes.
authorPaul Gilbert <dreammaster@scummvm.org>
Fri, 13 Sep 2019 19:15:20 +0000 (12:15 -0700)
committerDavid Griffith <dave@661.org>
Fri, 13 Sep 2019 19:52:10 +0000 (12:52 -0700)
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 <dreammaster@scummvm.org>.  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
src/sdl/sf_images.c
src/sdl/sf_resource.c
src/sdl/sf_video.c

index 85612239281734f052f623dbcfb2a38215580654..c2161edf159cf3cfb80726a74aea867fd0ee6917 100644 (file)
@@ -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
index 787bd944b293a9216221c976050cdcbb8d1a4a77..f577b1da1e438e3ee155b3cecb60f0668e242d92 100644 (file)
@@ -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;
   }
-
index 10f8c5055ec2be6259e6b125af7d7f37e1fcba35..a59d13c74708ecc5ca7d6ad8786f393382a99ae9 100644 (file)
@@ -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
 
index 533f6b25f4a35efb9919ea52c401989eaf325a79..565ad76300e6b0e5cecee7274dcb5f4ebd471912 100644 (file)
@@ -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<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;iy<m_gfxScale;iy++)
-         {
-         for (ix=0;ix<m_gfxScale;ix++) dst[ix] = dval;
-         dst += sbpitch;
-         }
-       }
-    src += pic->width;
+  /* 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;iy<m_gfxScale;iy++) {
+          for (ix=0;ix<m_gfxScale;ix++) dst[ix] = dval;
+          dst += sbpitch;
+        }
+      }
 
-  dirty = 1;
+      src += pic->width;
+    }
   }
 
+  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;
+}