public inbox for xconq7@sourceware.org
 help / color / mirror / Atom feed
* Improved scaler
@ 2004-12-01  2:34 mskala
  2004-12-01  5:32 ` Eric McDonald
  0 siblings, 1 reply; 2+ messages in thread
From: mskala @ 2004-12-01  2:34 UTC (permalink / raw)
  To: xconq-general, xconq7

[-- Attachment #1: Type: TEXT/PLAIN, Size: 4806 bytes --]

Attached is a patch (against current mainline CVS) implementing an
improved image scaler.  Since this is done inside imf.c, it should be
applicable to all interfaces.  In my own testing it seems to make XConq a
lot prettier in general, and it fixes the "terrain changes to flat colours
at highest magnification" problem.  It seems to work in both the TCL and
SDL interfaces, which are the only ones I can really test.  However, there
are some issues remaining:

* I'm 99% sure this problem is not caused by my patch because it seems to
exist in the mainline CVS also, but I discovered it while testing this
patch:  some shipped game modules cause the TCL/TK interface to crash with
a segfault.  See attached backtrace.  To replicate the problem, choose the
game "Cave of Wandering Death" in the TCL interface and choose the
defaults for variants and players; the segfault occurs just before the map
window comes up.  I'll probably log it into the SourceForge bug
tracker.  I've seen what seems to be the same bug with a few other game
modules but haven't been able to determine what's special about the ones
that exhibit it.  "Beirut 1982" also segfaults, but it seems like that may
be a different bug because the map window does pop up and messages appear
before it segfaults.

* Subimage scaling is still non-existant (not a new problem, but one that
is not fixed by this patch) so even if you define several alternate
terrain images, only one will be used at any magnifications where the
image must be generated by scaling.

* Some of the existing art doesn't really look very good with the new
scaler's selection rules.  In particular, some image families contain
tiles that clash visually with scaled versions of the non-tile images
from other image families.  To see an example, choose the default game in
the TCL interface with default settings except "world seen" turned on, and
look at the different zoom levels.  At one level, you'll see all the
terrain as tiles, including a nasty-looking brown and white triangle
pattern for mountains, blue with wavy lines for ocean, and yellow and
brown dots for desert.  All the other levels look more photographic, so
it's somewhat jarring to switch between the two.  This is really more an
issue of what's defined in the art than how the scaler works, but having
the scaler always choose the closest match means that the closest match
had better be the image we actually want to use.  A similar issue, though
I haven't actually seen it in practice, would be that if we defined both
colour and monochrome images in the same family, then the scaler might use
them both at different resolutions, to the possible annoyance of the user.

* Similarly: if you take a hexagon at 44x48 and you scale it up to 88x96,
the result will not be a perfect hexagon because the "jaggies" along the
slanted edges will have 2-pixel staircase steps instead of 1-pixel
staircase steps.  If you then attempt to cover a real 88x96 hexagon with
the scaler output, the result will leave some pixels around the edges
uncovered.  To see the effect of this issue, start an "advances" game in
the TCL interface and zoom in to the maximum.  The terrain images are only
defined at 44x48 resolution (maybe also at smaller resolutions) and so
when they're scaled up to 88x96, there are stray pixels along the slanted
edges, resulting in zigzag horizontal grey lines on the display.  I think,
ultimately, that this is an art problem; the art should be defined with
image data a little bigger than the hexagon instead of going up to the hex
edge and then stopping.  That way the scaler will be able to do its job.  
I'm not sure why we bother having masks in cell-terrain images.  The
system doesn't appear to need it; I've successfully used images covering
the entire image rectangle with no mask for cell terrain, and the
interfaces seem to correctly mask out the central hexagon anyway.  
They'll have to, when I get to the advanced terrain image stuff, or else
I'll have to generate hexagonal masks on the fly.

I may be able to edit the GIFs to add some extra pixels of appropriate
colour around the edges of the hexagons.  It seems like this issue affects
very few actual images among the ones we're distributing - the only
example I've found is advt44x48.gif.  I also might be able to tweak the
scaler to fake the extra pixels automatically so that no changes to the
art would be needed, but I'm less happy about that because it would mean
the scaler would have to *know* for sure whether a given image was going
to be used for cell terrain, and I can think of a lot of scenarios where
the automatic fill-in would generate bad data and cause more problems than
it would solve.
-- 
Matthew Skala
mskala@ansuz.sooke.bc.ca                    Embrace and defend.
http://ansuz.sooke.bc.ca/

[-- Attachment #2: Type: TEXT/PLAIN, Size: 20994 bytes --]

Index: kernel/imf.c
===================================================================
RCS file: /cvsroot/xconq/xconq/kernel/imf.c,v
retrieving revision 1.2
diff -c -r1.2 imf.c
*** kernel/imf.c	25 Nov 2004 06:59:29 -0000	1.2
--- kernel/imf.c	30 Nov 2004 05:28:07 -0000
***************
*** 32,44 ****
      K_OTHER_
  };
  
  static Image *get_subimg(ImageFamily *imf, int w, int h);
  static Image *largest_image(ImageFamily *imf);
  static int image_pixel_at(Image *img, int imtype, int x, int y);
  static void set_image_pixel_at(Image *img, int imtype, int x, int y, int val);
  static ImageFamily *new_imf(char *name);
- static Image *add_shrunken_image(ImageFamily *imf);
- static Image *add_magnified_image(ImageFamily *imf);
  static int bitmaps_match(int w, int h, Obj *lispdata, char *rawdata);
  static int color_matches_mono(Image *img);
  static void write_pixmap(FILE *fp, int w, int h, int aw, int ah,
--- 32,47 ----
      K_OTHER_
  };
  
+ typedef struct _MFEntry {
+     int value, count;
+ } MFEntry;
+ typedef MFEntry *ModeFilter;
+ 
  static Image *get_subimg(ImageFamily *imf, int w, int h);
  static Image *largest_image(ImageFamily *imf);
  static int image_pixel_at(Image *img, int imtype, int x, int y);
  static void set_image_pixel_at(Image *img, int imtype, int x, int y, int val);
  static ImageFamily *new_imf(char *name);
  static int bitmaps_match(int w, int h, Obj *lispdata, char *rawdata);
  static int color_matches_mono(Image *img);
  static void write_pixmap(FILE *fp, int w, int h, int aw, int ah,
***************
*** 50,55 ****
--- 53,65 ----
  static void write_palette_contents(FILE *fp, Obj *palette,
  				   int *rawpalette, int numcolors);
  static void write_color(FILE *fp, int n, int r, int g, int b);
+ static ModeFilter new_mf(int maxvals);
+ static void add_to_mf(ModeFilter mf, int val);
+ static int mode_of_mf(ModeFilter mf);
+ static void zero_mf(ModeFilter mf);
+ static void delete_mf(ModeFilter mf);
+ static Image *add_scaled_image(ImageFamily *imf, Image *img, int w, int h);
+ static int size_match_score(int wa, int ha, int wb, int hb);
  
  /* This is the array and count of known image families. */
  
***************
*** 753,893 ****
      }
  }
  
! /* If an image family includes no small images (16x16 or less), then
!    add an image that is half the size of the smallest image in the
!    family. */
  
! static Image *
! add_shrunken_image(ImageFamily *imf)
! {
!     int numbytes, numbytes2, x, y, sum, ix;
!     Image *img, *img2;
  
!     if (imf == NULL)
!       return NULL;
!     img = smallest_image(imf);
!     /* Don't try to shrink tiles or already-shrunken images or very
!        small images. */
!     if (img->istile || img->w <= 8 || img->h <= 8)
!       return NULL;
!     img2 = get_img(imf, img->w / 2, img->h / 2);
!     /* Chances are that any embedded subimage will become unrecognizable,
!        so leave embedname empty. */
!     if (img->embedx > 0)
!       img2->embedx = img->embedx / 2;
!     if (img->embedy > 0)
!       img2->embedy = img->embedy / 2;
!     if (img->embedw > 0)
!       img2->embedw = img->embedw / 2;
!     if (img->embedh > 0)
!       img2->embedh = img->embedh / 2;
!     if (img->rawcolrdata == NULL) {
! 	/* Try different ways to get some image data. */
! 	if (img->colrdata != lispnil) {
! 	    numbytes = img->h * computed_rowbytes(img->w, img->pixelsize);
! 	    img->rawcolrdata = (char *)xmalloc(numbytes);
! 	    interp_bytes(img->colrdata, numbytes, img->rawcolrdata, 0);
! 	} else if (img->filedata != lispnil) {
! 	    make_image_from_file_image(imf, img, img, 0);
! 	}
!     }
!     img2->pixelsize = img->pixelsize;
!     img2->palette = img->palette;
!     img2->rawpalette = img->rawpalette;
!     img2->numcolors = img->numcolors;
!     /* Mark the image as having been computed rather than read in. */
!     img2->synthetic = TRUE;
!     if (img->rawcolrdata != NULL) {
! 	numbytes2 = img2->h * computed_rowbytes(img2->w, img2->pixelsize);
! 	img2->rawcolrdata = (char *)xmalloc(numbytes2);
!     }
!     numbytes = img->h * computed_rowbytes(img->w, 1);
!     numbytes2 = img2->h * computed_rowbytes(img2->w, 1);
!     /* Ensure that binary version of mono image exists. */
!     make_raw_mono_data(img, FALSE);
!     if (img->rawmonodata != NULL)
!       img2->rawmonodata = (char *)xmalloc(numbytes2);
!     /* Ensure that binary version of mask exists. */
!     if (img->rawmaskdata == NULL && img->maskdata != lispnil) {
! 	img->rawmaskdata = (char *)xmalloc(numbytes);
! 	interp_bytes(img->maskdata, numbytes, img->rawmaskdata, 0);
      }
!     if (img->rawmaskdata != NULL)
!       img2->rawmaskdata = (char *)xmalloc(numbytes2);
!     /* Scan through the new image, computing each pixel separately. */
!     for (x = 0; x < img2->w; ++x) {
! 	for (y = 0; y < img2->h; ++y) {
! 	    ix = -1;
! 	    if (img2->rawcolrdata != NULL) {
! 		/* (should choose most common or else average colors) */
! 		int vals[4];
! 		vals[0] = image_pixel_at(img, K_COLR_, 2 * x,     2 * y);
! 		vals[1] = image_pixel_at(img, K_COLR_, 2 * x + 1, 2 * y);
! 		vals[2] = image_pixel_at(img, K_COLR_, 2 * x,     2 * y + 1);
! 		vals[3] = image_pixel_at(img, K_COLR_, 2 * x + 1, 2 * y + 1);
! 		ix = 0;
! 		if (vals[1] == vals[2] || vals[1] == vals[3])
! 		  ix = 1;
! 		else if (vals[2] == vals[3])
! 		  ix = 2;
! 		set_image_pixel_at(img2, K_COLR_, x, y, vals[ix]);
! 	    }
! 	    /* Add to the mono image if 2 or more of the 4 original
! 	       bits are on. */
! 	    if (img2->rawmonodata != NULL) {
! 		sum = 0;
! 		sum += image_pixel_at(img, K_MONO_, 2 * x,     2 * y);
! 		sum += image_pixel_at(img, K_MONO_, 2 * x + 1, 2 * y);
! 		sum += image_pixel_at(img, K_MONO_, 2 * x,     2 * y + 1);
! 		sum += image_pixel_at(img, K_MONO_, 2 * x + 1, 2 * y + 1);
! 		sum = (sum >= 2 ? 1 : 0);
! 		set_image_pixel_at(img2, K_MONO_, x, y, sum);
! 	    }
! 	    /* Use the value for the mask corresponding to the value
! 	       we used for the color image, or else the UL corner
! 	       value. */
! 	    if (img2->rawmaskdata != NULL) {
! 		int vals[4];
! 		vals[0] = image_pixel_at(img, K_MASK_, 2 * x,     2 * y);
! 		vals[1] = image_pixel_at(img, K_MASK_, 2 * x + 1, 2 * y);
! 		vals[2] = image_pixel_at(img, K_MASK_, 2 * x,     2 * y + 1);
! 		vals[3] = image_pixel_at(img, K_MASK_, 2 * x + 1, 2 * y + 1);
! 		if (ix < 0)
! 		  ix = 0;
! 		set_image_pixel_at(img2, K_MASK_, x, y, vals[ix]);
  	    }
! 	}
      }
-     compute_image_bbox(img2);
-     if (imf_interp_hook)
-       (*imf_interp_hook)(imf, img2, FALSE);
-     return img2;
  }
  
! /* Add an image twice the size of the largest image in the family, if
!    the family does not already include a large image. */
  
  static Image *
! add_magnified_image(ImageFamily *imf)
  {
!     int numbytes, numbytes2, x, y, x2, y2;
!     Image *img, *img2;
  
!     if (imf == NULL)
!       return NULL;
!     img = largest_image(imf);
!     if (img->istile || img->w >= 64 || img->h >= 64)
!       return NULL;
!     img2 = get_img(imf, img->w * 2, img->h * 2);
!     img2->embedname = img->embedname;
!     if (img->embedx > 0)
!       img2->embedx = img->embedx * 2;
!     if (img->embedy > 0)
!       img2->embedy = img->embedy * 2;
!     if (img->embedw > 0)
!       img2->embedw = img->embedw * 2;
!     if (img->embedh > 0)
!       img2->embedh = img->embedh * 2;
      if (img->rawcolrdata == NULL) {
  	/* Try different ways to get some image data. */
  	if (img->colrdata != lispnil) {
--- 763,919 ----
      }
  }
  
! /* Create a new data structure for mode filtering; maxvals is how many
!    distinct values we might call add_to_mf on.  The way this works is that
!    you create the structure with new_mf, then you call add_to_mf a bunch of
!    times with different values.  Then you can call mode_of_mf and it'll
!    tell which value you used the most times in calls to add_to_mf.  Calling
!    zero_mf resets the filter in a way that is cheaper than deleting and
!    re-creating it, and delete_mf is called when you're finally finished. */
! 
! static ModeFilter
! new_mf(int maxval)
! {
!     ModeFilter rval = (ModeFilter)xmalloc(sizeof(MFEntry)*(maxval+1));
!    
!     return rval;
! }
  
! /* Add a new value to the mode filter. */
  
! static void
! add_to_mf(ModeFilter mf, int val)
! {
!     int i;
!     MFEntry mfe;
!    
!     if (mf == NULL) return;
!     for (i = 0; (mf[i].value != val) && (mf[i].count != 0); i++);
!     mf[i].value = val;
!     mf[i].count++;
!     if (mf[i].count > mf[0].count) {
!         mfe = mf[i];
!         mf[i] = mf[0];
!         mf[0] = mfe;
      }
! }
! 
! /* Find the most common value in the mode filter. */
! 
! static int
! mode_of_mf(ModeFilter mf)
! {
!     if (mf != NULL)
!         return mf[0].value;
!     else
!         return 0;
! }
! 
! /* Empty the mode filter of all counts. */
! 
! static void
! zero_mf(ModeFilter mf)
! {
!     int i;
!    
!     if (mf == NULL) return;
!     for (i = 0; mf[i].count != 0; i++)
!         mf[i].count = 0;
!     mf[0].value = 0;
! }
! 
! /* Get rid of the mode filter. */
! 
! static void
! delete_mf(ModeFilter mf)
! {
!     if (mf != NULL) free(mf);
! }
! 
! /* Scale one layer of an image. */
! 
! static void
! scale_image_layer(Image *imgin, Image *imgout, int layer, int use_mask,
! 		  ModeFilter mf)
! {
!     int u, v, x, y, xa, xb, ya, yb, tmp;
! 
!    /* Perform a mode-filtered scaling operation. */
!    
!     /* The way this works is that u and v are coordinates in the output image
!        rval.  For each pixel (u,v) we compute the rectangle (xa..xb,ya..yb)
!        in the input image img, which rectangle covers all the pixels that are
!        touched by the pixel (u,v) when it's projected onto img, noting that
!        a pixel is defined to be closed on the sides with lesser coordinate
!        values and open on the sides with greater coordinate values.  We
!        perform a mode filter over (xa..xb,ya..yb), that is, we find the
!        pixel value that occurs most commonly in that rectangle, splitting
!        ties by preferring the value that occurs first in reading order; the
!        result is the pixel value for the pixel (u,v).  We do not count input
!        pixels that are masked out.  If the scaling is by less than a factor
!        of 2 up or down, the result will be basically the same as
!        nearest-neighbour resampling. */
!    
!     /* Loop for each pixel (u,v) */
!     for (v = 0; v < imgout->h; v++) {
!       
!         /* Might as well calculate ya and yb here because v determines them */
!         ya = (v*imgin->h)/imgout->h;
!         yb = ((v+1)*imgin->h-1)/imgout->h;
!       
!         for (u = 0; u < imgout->w; u++) {
! 
! 	    /* Calculate xa and xb */
! 	    xa = (u*imgin->w)/imgout->w;
! 	    xb = ((u+1)*imgin->w-1)/imgout->w;
! 	  
! 	    /* If the rectangle is small, and we don't have a mask, then
! 	       skip the mode filter business because the first pixel wins. */
! 	    if ((xb-xa+1)*(yb-ya+1) <= 2 && !use_mask) {
! 	        set_image_pixel_at(imgout, layer, u, v,
! 		    image_pixel_at(imgin, layer, xa, ya));
! 
! 	    /* Otherwise we have to do the filter thing. */
! 	    } else {
! 	      
! 	        /* Look through the input rectangle and compute modes */
! 	        for (y = yb; y >= ya; y--)
! 		    for (x = xb; x >= xa; x--)
! 		        if (!use_mask
! 			    || image_pixel_at(imgin, K_MASK_, x, y) != 0)
! 		        add_to_mf(mf, image_pixel_at(imgin, layer, x, y));
! 	      
! 	        /* Set output pixel */
! 		set_image_pixel_at(imgout, layer, u, v, mode_of_mf(mf));
! 		zero_mf(mf);
  	    }
!         }
      }
  }
  
! /* Scale image img in family imf to size w by h and add it to the family. */
  
  static Image *
! add_scaled_image(ImageFamily *imf, Image *img, int w, int h)
  {
!     Image *rval;
!     int numbytes, numbytes2;
!     ModeFilter mf;
!    
!     /* We must have a family and an input image, and the size must be sane. */
!     if (imf == NULL || img == NULL || w <= 0 || h <= 0)
!         return NULL;
!    
!     /* Create a data structure for the output. */
!     rval = get_img(imf, w, h);
! 
!     /* Scale the embedding coordinates. */
!     rval->embedx = (img->embedx*w)/img->w;
!     rval->embedy = (img->embedy*h)/img->h;
!     rval->embedw = (img->embedw*w)/img->w;
!     rval->embedh = (img->embedh*h)/img->h;
  
!     /* Actually get the input image if we haven't yet. */
      if (img->rawcolrdata == NULL) {
  	/* Try different ways to get some image data. */
  	if (img->colrdata != lispnil) {
***************
*** 898,944 ****
  	    make_image_from_file_image(imf, img, img, 0);
  	}
      }
!     img2->pixelsize = img->pixelsize;
!     img2->palette = img->palette;
!     img2->rawpalette = img->rawpalette;
!     img2->numcolors = img->numcolors;
!     img2->synthetic = TRUE;
      if (img->rawcolrdata != NULL) {
! 	numbytes2 = img2->h * computed_rowbytes(img2->w, img2->pixelsize);
! 	img2->rawcolrdata = (char *)xmalloc(numbytes2);
      }
      numbytes = img->h * computed_rowbytes(img->w, 1);
!     numbytes2 = img2->h * computed_rowbytes(img2->w, 1);
      make_raw_mono_data(img, FALSE);
      if (img->rawmonodata != NULL)
!       img2->rawmonodata = (char *)xmalloc(numbytes2);
!     if (img->rawmaskdata == NULL && img->maskdata != lispnil ) {
  	img->rawmaskdata = (char *)xmalloc(numbytes);
  	interp_bytes(img->maskdata, numbytes, img->rawmaskdata, 0);
      }
      if (img->rawmaskdata != NULL)
!       img2->rawmaskdata = (char *)xmalloc(numbytes2);
!     for (x = 0; x < img2->w; ++x) {
! 	for (y = 0; y < img2->h; ++y) {
! 	    x2 = x >> 1;  y2 = y >> 1;
! 	    if (img->rawcolrdata != NULL) {
! 		set_image_pixel_at(img2, K_COLR_, x, y,
! 				   image_pixel_at(img, K_COLR_, x2, y2));
! 	    }
! 	    if (img->rawmonodata != NULL) {
! 		set_image_pixel_at(img2, K_MONO_, x, y,
! 				   image_pixel_at(img, K_MONO_, x2, y2));
! 	    }
! 	    if (img->rawmaskdata != NULL) {
! 		set_image_pixel_at(img2, K_MASK_, x, y,
! 				   image_pixel_at(img, K_MASK_, x2, y2));
! 	    }
! 	}
!     }
!     compute_image_bbox(img2);
      if (imf_interp_hook)
!       (*imf_interp_hook)(imf, img2, FALSE);
!     return img2;
  }
  
  void
--- 924,983 ----
  	    make_image_from_file_image(imf, img, img, 0);
  	}
      }
! 
!     /* Copy over a bunch of other fields. */
!     rval->pixelsize = img->pixelsize;
!     rval->palette = img->palette;
!     rval->rawpalette = img->rawpalette;
!     rval->numcolors = img->numcolors;
! 
!     /* Mark the image as having been computed rather than read in. */
!     rval->synthetic = TRUE;
! 
      if (img->rawcolrdata != NULL) {
! 	numbytes2 = rval->h * computed_rowbytes(rval->w, rval->pixelsize);
! 	rval->rawcolrdata = (char *)xmalloc(numbytes2);
      }
      numbytes = img->h * computed_rowbytes(img->w, 1);
!     numbytes2 = rval->h * computed_rowbytes(rval->w, 1);
! 
!     /* Ensure that binary version of mono image exists. */
      make_raw_mono_data(img, FALSE);
      if (img->rawmonodata != NULL)
!       rval->rawmonodata = (char *)xmalloc(numbytes2);
! 
!     /* Ensure that binary version of mask exists. */
!     if (img->rawmaskdata == NULL && img->maskdata != lispnil) {
  	img->rawmaskdata = (char *)xmalloc(numbytes);
  	interp_bytes(img->maskdata, numbytes, img->rawmaskdata, 0);
      }
      if (img->rawmaskdata != NULL)
!       rval->rawmaskdata = (char *)xmalloc(numbytes2);
!    
!     mf = new_mf(256);
!     if (rval->rawmaskdata != NULL) {
!         if (rval->rawcolrdata != NULL)
! 	    scale_image_layer(img, rval, K_COLR_, TRUE, mf);
!         if (rval->rawmonodata != NULL)
! 	    scale_image_layer(img, rval, K_MONO_, TRUE, mf);
!         scale_image_layer(img, rval, K_MASK_, FALSE, mf);
!     } else {
!         if (rval->rawcolrdata != NULL)
! 	    scale_image_layer(img, rval, K_COLR_, FALSE, mf);
!         if (rval->rawmonodata != NULL)
! 	    scale_image_layer(img, rval, K_MONO_, FALSE, mf);
!     }
!     delete_mf(mf);
!    
!     /* Set the image bounding box */
!     compute_image_bbox(rval);
!    
!     /* Call the interface hook */
      if (imf_interp_hook)
!         (*imf_interp_hook)(imf, rval, FALSE);
!    
!     /* Return result */
!     return rval;
  }
  
  void
***************
*** 989,1050 ****
      }
  }
  
! /* Try to find the best of multiple images for the given bounding box.
!    Don't return anything that won't fit in min space. */
  
  Image *
  best_image(ImageFamily *imf, int w, int h)
  {
!     Image *img, *best = NULL, *fallback = NULL, *best_tile = NULL;
  
      if (imf == NULL || imf->images == NULL)
!       return NULL;
      for_all_images(imf, img) {
!     	/* Skip all basic terrain images except power 4 and 5
!     	if we are low on memory. */
      	if (poor_memory
      	    && img->isterrain
      	    && img->w != 24
      	    && img->w != 44) {
      		continue;
      	}
! 	/* Exact matches need no further searching. */
  	if (w == img->w && h == img->h && !img->istile) {
  		return img;
  	}
! 	/* Find the best size of image. */
! 	if (best == NULL
! 	    || (img->w <= w && img->h <= h
! 		&& img->w > best->w && img->h > best->h)) {
! 		best = img;
! 	}
! 	/* For tiles, pick out the largest tile. */
! 	if (img->istile) {
! 	    if (best_tile == NULL
! 		|| (img->w > best_tile->w && img->h > best_tile->h)) {
! 		best_tile = img;
  	    }
  	}
      }
!     /* If there were any tiles at all for this image, we're probably
!        here because it's a terrain image for which no exact matches
!        were found; so return the largest tile instead. */
!     if (best_tile != NULL)
!       return best_tile;
!     /* If the best image is too large or too small (at this point we
!        know it's not a tiling pattern), scale the image and return that. */
!     if (best->w > w && best->h > h) {
! 	best = add_shrunken_image(imf);
! 	if (best != NULL && best->w > w && best->h > h) {
! 	      best = add_shrunken_image(imf);
! 	}
!     } else if (best->w <= (w >> 1) && best->h <= (h >> 1)) {
! 	fallback = add_magnified_image(imf);
! 	if (fallback != NULL) { 
! 		best = fallback;
!     	}
!     }
!     return best;
  }
  
  Image *
--- 1028,1102 ----
      }
  }
  
! /* Compute a score describing how much scaling or tiling a given image will
!    need to match a given size. */
! 
! static int
! size_match_score(int wa, int ha, int wb, int hb)
! {
!     return (wa*100)/wb + (wb*100)/wa + (ha*100)/hb + (hb*100)/ha - 400;
! }
! 
! /* Try to find an image of the given size from the family, generating it
!    by scaling if necessary. */
  
  Image *
  best_image(ImageFamily *imf, int w, int h)
  {
!     Image *img, *best_nonsynth = NULL, *best_tile = NULL;
!     int best_nonsynth_score, best_tile_score, s;
  
      if (imf == NULL || imf->images == NULL)
!         return NULL;
! 
      for_all_images(imf, img) {
! 
!         /* Skip all basic terrain images except power 4 and 5
! 	   if we are low on memory. */
      	if (poor_memory
      	    && img->isterrain
      	    && img->w != 24
      	    && img->w != 44) {
      		continue;
      	}
! 
!         /* Exact matches need no further searching. */
  	if (w == img->w && h == img->h && !img->istile) {
  		return img;
  	}
! 
!         /* Find best image that isn't synthetic or tile (scaling candidate) */
!         if (!img->istile && !img->synthetic) {
! 	    s = size_match_score(w, h, img->w, img->h);
! 	    if (best_nonsynth == NULL || s < best_nonsynth_score) {
! 	        best_nonsynth = img;
! 	        best_nonsynth_score = s;
! 	    }
! 	}
! 
!         /* Find best tile */
!         if (img->istile) {
! 	    s = size_match_score(w, h, img->w, img->h);
! 	    if (best_tile == NULL || s < best_tile_score) {
! 	        best_tile = img;
! 	        best_tile_score = s;
  	    }
  	}
      }
! 
!     /* If we have a tile, and we're low on memory, we can't scale, or
!        the best scaling candidate isn't as good as the tile, use tile. */
!     if (best_tile != NULL && (poor_memory || best_nonsynth == NULL ||
! 			      best_tile_score < best_nonsynth_score))
!         return best_tile;
!    
!     /* Now we know we want to scale, so there had better exist a candidate.
!        If not, let the caller worry about it. */
!     if (best_nonsynth == NULL)
!         return NULL;
! 
!     /* Scale the candidate. */
!     return add_scaled_image(imf, best_nonsynth, w, h);
  }
  
  Image *

[-- Attachment #3: Type: TEXT/PLAIN, Size: 4226 bytes --]

GNU gdb 5.3
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-slackware-linux"...
(gdb) run
Starting program: /usr/local/bin/xconq 

Program received signal SIGSEGV, Segmentation fault.
0x4018814c in Tcl_SetResult () from /usr/lib/libtcl8.4.so
(gdb) bt
#0  0x4018814c in Tcl_SetResult () from /usr/lib/libtcl8.4.so
#1  0x0804cb63 in tk_u_image_name(void*, Tcl_Interp*, int, char**) (
    cldata=0x0, interp=0x820c660, argc=2, argv=0x0) at tkmain.c:849
#2  0x40132cbb in TclInvokeStringCommand () from /usr/lib/libtcl8.4.so
#3  0x40133dba in TclEvalObjvInternal () from /usr/lib/libtcl8.4.so
#4  0x4015824e in TclExecuteByteCode () from /usr/lib/libtcl8.4.so
#5  0x40157920 in TclCompEvalObj () from /usr/lib/libtcl8.4.so
#6  0x401868e4 in TclObjInterpProc () from /usr/lib/libtcl8.4.so
#7  0x40133dba in TclEvalObjvInternal () from /usr/lib/libtcl8.4.so
#8  0x4015824e in TclExecuteByteCode () from /usr/lib/libtcl8.4.so
#9  0x40157920 in TclCompEvalObj () from /usr/lib/libtcl8.4.so
#10 0x401868e4 in TclObjInterpProc () from /usr/lib/libtcl8.4.so
#11 0x40133dba in TclEvalObjvInternal () from /usr/lib/libtcl8.4.so
#12 0x40134747 in Tcl_EvalEx () from /usr/lib/libtcl8.4.so
#13 0x40134b13 in Tcl_Eval () from /usr/lib/libtcl8.4.so
#14 0x4013609f in Tcl_GlobalEval () from /usr/lib/libtcl8.4.so
#15 0x08055341 in eval_tcl_cmd(char*, ...) (
    fmt=0x2 <Address 0x2 out of bounds>) at tkmain.c:3902
#16 0x0805580a in create_map() () at tkmain.c:4054
#17 0x08056e1f in init_display() () at tkinit.c:278
#18 0x0805571a in init_all_displays () at tkmain.c:4011
#19 0x080569c8 in launch_game() () at tkmain.c:4649
#20 0x0804dfab in tk_launch_game(void*, Tcl_Interp*, int, char**) (cldata=0x0, 
    interp=0x820c660, argc=1, argv=0xbfffdfe0) at tkmain.c:1406
#21 0x40132cbb in TclInvokeStringCommand () from /usr/lib/libtcl8.4.so
#22 0x40133dba in TclEvalObjvInternal () from /usr/lib/libtcl8.4.so
#23 0x4015824e in TclExecuteByteCode () from /usr/lib/libtcl8.4.so
#24 0x40157920 in TclCompEvalObj () from /usr/lib/libtcl8.4.so
#25 0x401868e4 in TclObjInterpProc () from /usr/lib/libtcl8.4.so
#26 0x40133dba in TclEvalObjvInternal () from /usr/lib/libtcl8.4.so
#27 0x4015824e in TclExecuteByteCode () from /usr/lib/libtcl8.4.so
#28 0x40157920 in TclCompEvalObj () from /usr/lib/libtcl8.4.so
#29 0x40134ca1 in Tcl_EvalObjEx () from /usr/lib/libtcl8.4.so
#30 0x4021139e in TkInvokeButton () from /usr/lib/libtk8.4.so
#31 0x40210644 in ButtonWidgetObjCmd () from /usr/lib/libtk8.4.so
#32 0x40133dba in TclEvalObjvInternal () from /usr/lib/libtcl8.4.so
#33 0x40133fbe in Tcl_EvalObjv () from /usr/lib/libtcl8.4.so
#34 0x40134c44 in Tcl_EvalObjEx () from /usr/lib/libtcl8.4.so
#35 0x40186245 in Tcl_UplevelObjCmd () from /usr/lib/libtcl8.4.so
#36 0x40133dba in TclEvalObjvInternal () from /usr/lib/libtcl8.4.so
#37 0x4015824e in TclExecuteByteCode () from /usr/lib/libtcl8.4.so
#38 0x40157920 in TclCompEvalObj () from /usr/lib/libtcl8.4.so
#39 0x401868e4 in TclObjInterpProc () from /usr/lib/libtcl8.4.so
#40 0x40133dba in TclEvalObjvInternal () from /usr/lib/libtcl8.4.so
#41 0x40134747 in Tcl_EvalEx () from /usr/lib/libtcl8.4.so
#42 0x40134b13 in Tcl_Eval () from /usr/lib/libtcl8.4.so
#43 0x4013609f in Tcl_GlobalEval () from /usr/lib/libtcl8.4.so
#44 0x401cf04a in Tk_BindEvent () from /usr/lib/libtk8.4.so
#45 0x401d4c87 in TkBindEventProc () from /usr/lib/libtk8.4.so
#46 0x401dc293 in Tk_HandleEvent () from /usr/lib/libtk8.4.so
#47 0x401dc6ed in WindowEventProc () from /usr/lib/libtk8.4.so
#48 0x4017ccaa in Tcl_ServiceEvent () from /usr/lib/libtcl8.4.so
#49 0x4017cf1a in Tcl_DoOneEvent () from /usr/lib/libtcl8.4.so
#50 0x08050005 in ui_mainloop() () at tkmain.c:2032
#51 0x0804af75 in main (argc=1, argv=0xbffff874) at tkunix.c:118
#52 0x4037ebb4 in __libc_start_main () from /lib/libc.so.6
(gdb) quit
The program is running.  Exit anyway? (y or n) 

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: Improved scaler
  2004-12-01  2:34 Improved scaler mskala
@ 2004-12-01  5:32 ` Eric McDonald
  0 siblings, 0 replies; 2+ messages in thread
From: Eric McDonald @ 2004-12-01  5:32 UTC (permalink / raw)
  To: mskala; +Cc: xconq-hackers, xconq7

mskala@ansuz.sooke.bc.ca wrote:

> * I'm 99% sure this problem is not caused by my patch because it seems to
> exist in the mainline CVS also, but I discovered it while testing this
> patch:  some shipped game modules cause the TCL/TK interface to crash with
> a segfault.  See attached backtrace.  To replicate the problem, choose the
> game "Cave of Wandering Death" in the TCL interface and choose the
> defaults for variants and players; the segfault occurs just before the map
> window comes up.  I'll probably log it into the SourceForge bug
> tracker.  I've seen what seems to be the same bug with a few other game
> modules but haven't been able to determine what's special about the ones
> that exhibit it.  "Beirut 1982" also segfaults, but it seems like that may
> be a different bug because the map window does pop up and messages appear
> before it segfaults.

I'll look into these.

> This is really more an
> issue of what's defined in the art than how the scaler works, but having
> the scaler always choose the closest match means that the closest match
> had better be the image we actually want to use. 

Right. I think we can live with this for now. Perhaps some games can be 
made to use different terrain tile sets (where appropriate). The 
existing tile sets might also be able to built for other resolutions by 
taking a copy of them, rescaling them to the new resolution, then 
acquiring brushes based on the patterns in the tiles at the original 
scaling, and using those brushes to paint in the better-looking patterns 
at the new scaling.

>The terrain images are only
> defined at 44x48 resolution (maybe also at smaller resolutions) and so
> when they're scaled up to 88x96, there are stray pixels along the slanted
> edges, resulting in zigzag horizontal grey lines on the display.  I think,
> ultimately, that this is an art problem; the art should be defined with
> image data a little bigger than the hexagon instead of going up to the hex
> edge and then stopping.  

[snipped]

> I may be able to edit the GIFs to add some extra pixels of appropriate
> colour around the edges of the hexagons.  It seems like this issue affects
> very few actual images among the ones we're distributing - the only
> example I've found is advt44x48.gif.  I also might be able to tweak the
> scaler to fake the extra pixels automatically so that no changes to the
> art would be needed, but I'm less happy about that because it would mean
> the scaler would have to *know* for sure whether a given image was going
> to be used for cell terrain, 

Hmmm.... Could the 'terrain' keyword, which is used in the .imf for the 
advterr tiles, IIRC, be taken as a hint to fill in the jaggies 
automatically?

> Index: kernel/imf.c
> ===================================================================
> RCS file: /cvsroot/xconq/xconq/kernel/imf.c,v
> retrieving revision 1.2
> diff -c -r1.2 imf.c
> *** kernel/imf.c	25 Nov 2004 06:59:29 -0000	1.2
> --- kernel/imf.c	30 Nov 2004 05:28:07 -0000
> ***************
> --- 32,47 ----
>       K_OTHER_
>   };
>   
> + typedef struct _MFEntry {
> +     int value, count;
> + } MFEntry;
> + typedef MFEntry *ModeFilter;
> + 

This patch seems good to me. The only thing that we might wish to change 
is "_MFEntry" to "a_mf_entry". Xconq seems to use this convention for 
such struct definitions, and I think that using a leading underscore 
could get us into trouble (not in this particular case) with the 
internals of some libraries. For example, the GNU standard C/C++ and 
system headers seem to often use this convention either for internal 
work or providing an alternative definition when strict ANSI compliance 
is set. Of course, since we now compile Xconq with a C++ compiler, there 
  may not be a need to do the typedef at all.

Other than that, things look fine, insofar as I read them in any detail. 
I will apply and test the patch soon.

   Thanks,
     Eric

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2004-12-01  2:34 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2004-12-01  2:34 Improved scaler mskala
2004-12-01  5:32 ` Eric McDonald

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).