mirror of
				https://git.code.sf.net/p/libpng/code.git
				synced 2025-07-10 18:04:09 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			239 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* Given a target range and a source range work out an expression to scale from
 | 
						|
 * the source to the target of the form:
 | 
						|
 *
 | 
						|
 *    (number * mult + add)>>16
 | 
						|
 *
 | 
						|
 * The command arguments are:
 | 
						|
 *
 | 
						|
 *    scale target source
 | 
						|
 *
 | 
						|
 * and the program works out a pair of numbers, mult and add, that evaluate:
 | 
						|
 *
 | 
						|
 *          number * target
 | 
						|
 *   round( --------------- )
 | 
						|
 *              source
 | 
						|
 *
 | 
						|
 * exactly for number in the range 0..source
 | 
						|
 */
 | 
						|
#define _ISOC99_SOURCE 1
 | 
						|
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <math.h>
 | 
						|
 | 
						|
static double minerr;
 | 
						|
static unsigned long minmult, minadd, minshift;
 | 
						|
static long mindelta;
 | 
						|
 | 
						|
static int
 | 
						|
test(unsigned long target, unsigned long source, unsigned long mult,
 | 
						|
   long add, unsigned long shift, long delta)
 | 
						|
{
 | 
						|
   unsigned long i;
 | 
						|
   double maxerr = 0;
 | 
						|
   double rs = (double)target/source;
 | 
						|
 | 
						|
   for (i=0; i<=source; ++i)
 | 
						|
   {
 | 
						|
      unsigned long t = i*mult+add;
 | 
						|
      double err = fabs((t >> shift) - i*rs);
 | 
						|
 | 
						|
      if (err > minerr)
 | 
						|
         return 0;
 | 
						|
 | 
						|
      if (err > maxerr)
 | 
						|
         maxerr = err;
 | 
						|
   }
 | 
						|
 | 
						|
   if (maxerr < minerr)
 | 
						|
   {
 | 
						|
      minerr = maxerr;
 | 
						|
      minmult = mult;
 | 
						|
      minadd = add;
 | 
						|
      minshift = shift;
 | 
						|
      mindelta = delta;
 | 
						|
   }
 | 
						|
 | 
						|
   return maxerr < .5;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
dotest(unsigned long target, unsigned long source, unsigned long mult,
 | 
						|
   long add, unsigned long shift, long delta, int print)
 | 
						|
{
 | 
						|
   if (test(target, source, mult, add, shift, delta))
 | 
						|
   {
 | 
						|
      if (print & 4)
 | 
						|
         printf("      {%11lu,%6ld /* >>%lu */ }, /* %lu/%lu */\n",
 | 
						|
            mult, add, shift, target, source);
 | 
						|
 | 
						|
      else if (print & 2)
 | 
						|
         printf("      {%11lu,%6ld,%3lu }, /* %lu/%lu */\n",
 | 
						|
            mult, add, shift, target, source);
 | 
						|
 | 
						|
      else if (print)
 | 
						|
         printf("number * %lu/%lu = (number * %lu + %ld) >> %lu [delta %ld]\n",
 | 
						|
            target, source, mult, add, shift, delta);
 | 
						|
 | 
						|
      return 1;
 | 
						|
   }
 | 
						|
 | 
						|
   return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
find(unsigned long target, unsigned long source, int print, int fixshift)
 | 
						|
{
 | 
						|
   unsigned long shift = 0;
 | 
						|
   unsigned long shiftlim = 0;
 | 
						|
 | 
						|
   /* In the final math the sum is at most (source*mult+add) >> shift, so:
 | 
						|
    *
 | 
						|
    *    source*mult+add < 1<<32
 | 
						|
    *    mult < (1<<32)/source
 | 
						|
    *
 | 
						|
    * but:
 | 
						|
    *
 | 
						|
    *    mult = (target<<shift)/source
 | 
						|
    *
 | 
						|
    * so:
 | 
						|
    *
 | 
						|
    *    (target<<shift) < (1<<32)
 | 
						|
    */
 | 
						|
   if (fixshift < 0)
 | 
						|
      while ((target<<shiftlim) < 0x80000000U) ++shiftlim;
 | 
						|
 | 
						|
   else
 | 
						|
      shift = shiftlim = (unsigned long)fixshift;
 | 
						|
 | 
						|
   minerr = 1E8;
 | 
						|
 | 
						|
   for (; shift<=shiftlim; ++shift)
 | 
						|
   {
 | 
						|
      unsigned long mult = ((target<<shift) + (source>>1)) / source;
 | 
						|
      long delta;
 | 
						|
      long limit = 1; /* seems to be sufficient */
 | 
						|
      long add, start, end;
 | 
						|
 | 
						|
      end = 1<<shift;
 | 
						|
      start = -end;
 | 
						|
 | 
						|
      for (add=start; add<=end; ++add)
 | 
						|
         if (dotest(target,source,mult,add,shift,0,print))
 | 
						|
            return 1;
 | 
						|
 | 
						|
      for (delta=1; delta<=limit; ++delta)
 | 
						|
      {
 | 
						|
#        if 0
 | 
						|
            fprintf(stderr, "%lu/%lu: shift %lu, delta %lu\n", target, source,
 | 
						|
               shift, delta);
 | 
						|
#        endif
 | 
						|
 | 
						|
         for (add=start; add<=end; ++add)
 | 
						|
         {
 | 
						|
            if (dotest(target, source, mult-delta, add, shift, -delta, print))
 | 
						|
               return 1;
 | 
						|
 | 
						|
            if (dotest(target, source, mult+delta, add, shift, delta, print))
 | 
						|
               return 1;
 | 
						|
         }
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   if (print & 4)
 | 
						|
      printf("      {%11lu,%6ld /* >>%lu */ }, /* %lu/%lu ERROR: .5+%g*/\n",
 | 
						|
         minmult, minadd, minshift, target, source, minerr-.5);
 | 
						|
 | 
						|
   else if (print & 2)
 | 
						|
      printf("      {%11lu,%6ld,%3lu }, /* %lu/%lu ERROR: .5+%g*/\n",
 | 
						|
         minmult, minadd, minshift, target, source, minerr-.5);
 | 
						|
 | 
						|
   else if (print)
 | 
						|
      printf(
 | 
						|
         "number * %lu/%lu ~= (number * %lu + %ld) >> %lu +/-.5+%g [delta %ld]\n",
 | 
						|
         target, source, minmult, minadd, minshift, minerr-.5, mindelta);
 | 
						|
 | 
						|
   return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
usage(const char *prog)
 | 
						|
{
 | 
						|
   fprintf(stderr,
 | 
						|
      "usage: %s {--denominator|--maxshift|--code} target {source}\n"
 | 
						|
      " For each 'source' prints 'mult' and 'add' such that:\n\n"
 | 
						|
      "   (number * mult + add) >> 16 = round(number*target/source)\n\n"
 | 
						|
      " for all integer values of number in the range 0..source.\n\n"
 | 
						|
      " --denominator: swap target and source (specify a single source first\n"
 | 
						|
      "                and follow with multiple targets.)\n"
 | 
						|
      "    --maxshift: find the lowest shift value that works for all the\n"
 | 
						|
      "                repeated 'source' values\n"
 | 
						|
      "        --code: output C code for array/structure initialization\n",
 | 
						|
      prog);
 | 
						|
   exit(1);
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
main(int argc, const char **argv)
 | 
						|
{
 | 
						|
   int i, err = 0, maxshift = 0, firstsrc = 1, code = 0, denominator = 0;
 | 
						|
   unsigned long target, shift = 0;
 | 
						|
 | 
						|
   while (argc > 1)
 | 
						|
   {
 | 
						|
      if (strcmp(argv[firstsrc], "--maxshift") == 0)
 | 
						|
      {
 | 
						|
         maxshift = 1;
 | 
						|
         ++firstsrc;
 | 
						|
      }
 | 
						|
 | 
						|
      else if (strcmp(argv[firstsrc], "--code") == 0)
 | 
						|
      {
 | 
						|
         code = 1;
 | 
						|
         ++firstsrc;
 | 
						|
      }
 | 
						|
 | 
						|
      else if (strcmp(argv[firstsrc], "--denominator") == 0)
 | 
						|
      {
 | 
						|
         denominator = 1;
 | 
						|
         ++firstsrc;
 | 
						|
      }
 | 
						|
 | 
						|
      else
 | 
						|
         break;
 | 
						|
   }
 | 
						|
 | 
						|
 | 
						|
   if (argc < 2+firstsrc)
 | 
						|
      usage(argv[0]);
 | 
						|
 | 
						|
   target = strtoul(argv[firstsrc++], 0, 0);
 | 
						|
   if (target == 0) usage(argv[0]);
 | 
						|
 | 
						|
   for (i=firstsrc; i<argc; ++i)
 | 
						|
   {
 | 
						|
      unsigned long source = strtoul(argv[i], 0, 0);
 | 
						|
 | 
						|
      if (source == 0) usage(argv[0]);
 | 
						|
 | 
						|
      if (!find(denominator ? source : target, denominator ? target : source,
 | 
						|
         maxshift ? 0 : 1+code, -1))
 | 
						|
         err = 1;
 | 
						|
 | 
						|
      if (minshift > shift) shift = minshift;
 | 
						|
   }
 | 
						|
 | 
						|
   if (maxshift) for (i=firstsrc; i<argc; ++i)
 | 
						|
   {
 | 
						|
      unsigned long source = strtoul(argv[i], 0, 0);
 | 
						|
 | 
						|
      if (!find(denominator ? source : target, denominator ? target : source,
 | 
						|
         code ? 4 : 1, shift))
 | 
						|
         err = 1;
 | 
						|
   }
 | 
						|
 | 
						|
   /* Just an exit code - the printout above lists the problem */
 | 
						|
   return err;
 | 
						|
}
 |