documentation : source codes : download

pixrescale is a program with associated scripts for generating images at the sizes needed for pix.js. It is intended for users who are able to compile and install software on their own computers and then run it from the command line. If you can only click and double-click, then it isn’t for you.

This page contains a number of utilities for producing and modifying the images needed by pix.js.

The most useful, for new users, is pixdownscale (both an executable program and a script), which, starting from the largest image in a series, generates all the others. The executable program does this for a single series whereas the script does it in bulk.

The remaining programs generate new items (icons for routemaster) or regenerate existing ones without exif bloat. They are particularly useful for people like me who originally created jpgs manually and need to make bulk changes.

Note that while pix.js is perfectly general, the software on this page follows my own conventions for names and sizes of jpg images. It would be easy to change it to match other conventions, but would need more effort to make it fully general.

Firstly you need the code itself. Go to the download link and click on it. This will download a file ‘pixrescale.tar’ onto your computer. Move it to wherever you want to do the compilation and extract its entries (by ‘tar -xvf pixrescale.tar’ or double-clicking). This will create number of .c, .h, and .sh files. Make sure you are in the directory containing them.

Now you have to build the executable pixrescale. There are three ways of achieving this: an easy method and two (very similar) hard methods.

The easy method uses compact open-source jpeg I/O functions by Martin Fiedler (nanojpeg) and Jon Olick (jpeg). Copies of their software were present in the tar file you expanded. To compile pixrescale you write:

g++ -o pixrescale -g -O pixrescale.c tinyreadjpg.c nanojpeg.c jpg.c rescale.c

(or something similar if you use a different compiler). This produces an executable pixrescale (and may also shower you with idiotic warning messages – if you see any errors, then something has gone wrong).

Move pixrescale to a directory present in your Unix path (or do whatever is equivalent on another operating system).

[If you haven’t got a compiler, then you need to download one. I use g++. To install it on a Mac you need homebrew. If you haven’t installed homebrew, then you need to do so first. Then type brew install gcc to install gcc, following any instructions it gives you.]

The drawback of the easy install is that the resulting code doesn’t handle progressive jpegs (you get an error message for each one). You shouldn’t be creating such files, but I find that I have a few, presumably through finger trouble. I could make a virtue of the limitation by taking it as a cue to convert the relevant images to non-progressive jpg (eg. using ffmpeg), but it makes life easier for me to use the libjpeg install.

Installing libjpeg on a Mac is fairly easy: just type brew install libjpeg. On another operating system you’re on your own: on Linux it should be easy; on Windows... rather you than me.

The difficulty comes when you want to include the compiled modules in your own executable. I found that I had to do some of this manually (and I’m not alone in having problems: see stackoverflow). The solution I found was to locate the include files and static library produced by the installation and grab them to where I wanted them. I found that the header files were in /opt/homebrew/opt/jpeg/include, so I copied them all to my own working directory, i.e.

cp /opt/homebrew/opt/jpeg/include/*.h .

I found that the library was /opt/homebrew/Cellar/jpeg/9f/lib/libjpeg.a so I incorporated that into the compile line.

So, having copied the header files, I ran the command

g++ -o pixrescale -g -O /opt/homebrew/Cellar/jpeg/9f/lib/libjpeg.a pixrescale.c readjpg.c rescale.c

and this produced a pixrescale executable using the ‘libjpeg’ functions.

Presumably an analogous procedure will enable you to build pixrescale using libjpeg-turbo. I find that I already have the latter on my Macbook, perhaps as a byproduct of having installed ffmpeg. I haven’t tried building it into pixrescale. Hopefully the main interface is calling-sequence compatible with libjpeg but the code is faster.

pixrescale is one of four executable programs contained in the tarfile. The other three – pixcompact, pixdownscale and pixrewrite – should be installed in exactly the same way, eg. typing

g++ -o pixcompact -g -O /opt/homebrew/Cellar/jpeg/9f/lib/libjpeg.a pixcompact.c readjpg.c rescale.c

There are also three bash scripts, namely pixicon.sh, pixcompact.sh, and pixdownscale.sh. You should run

chmod +x *.sh

to make them executable and then move them into a directory present in your execution path.

All three programs can be run directly from the command line, although their main purpose is for use in scripts.

The usual call line is

pixrescale infile outfile quality newwidth newheight

This creates a new jpg outfile from the input file given, rescaled to the new width and height (cropping if necessary to preserve shape). If you omit the new width and height, you get a copy which is the same size as the original. The quality is an integer from 1 to 100, with 100 being best. I use 60 for images which may be viewed at one physical pixel per logical pixel, and 50 for images which I expect never to be viewed except at higher resolutions.

If you like you can omit the new width and instead provide a reduction factor preceded by a ‘/’, eg.

pixrescale infile outfile quality /2

which reduces linear dimensions by a factor of 2.

The output file of pixrescale is allowed to overwrite its inout file, and if you omit it, this is what will happen. Thus

pixrescale infile

simply overwrites infile with the same image (but no exif). pixrewrite can be used for the same purpose.

 Thumbnail (140x196)    
Icon (52x52) produced by
pixrescale from thumbnail
Cropped thumbnail
(140x140) produced
by ffmpeg from
thumbnail
Icon produced by pixrescale
from cropped thumbnail
Icon as displayed (1kB)
In common with the other programs and scripts here, pixrescale produces output jpgs with no exif (which is the metadata containing the photograph’s date, shutter speed, etc.). Since you would like to keep some of this, make sure you retain a large version of an image in addition to whatever reduced versions come out of my rescaling code. Discarding the exif is in fact a blessing since the exif bloats small images: a 52×52 icon generated by Photoshop is 5% image, 95% bloat. I think that this is because the metadata contains a thumbnail which can easily be larger than the image itself.

Therefore one reason for using pixrescale is that you might have an image which you suspect of being bloated, and which you want to reduce to a sensible size.

pixrescale differs from my other programs and scripts in making no assumptions about the scale factors and suffices used to distinguish the various scalings. The assumptions made by other software will be mentioned in a moment.

Another reason for using pixrescale on its own is to correct unsatisfactory results from bulk processing. The software in this library crops images when the desired new size has a different aspect ration than the original, centring the image in each case. Sometimes this is not what you want, and you may try to correct it manually. I do this in two stages. First I reduce the original to the desired aspect ratio using a suitable image editor (ffmpeg), then I rescale it using pixrescale. This way I avoid bloat. (I could add more complicated calling sequences to pixrescale, but is it worth it?)

The example on the left shows an icon as it might be produced by bulk processing, and the steps by which I produce a better version using ffmpeg and pixrescale. (If the images look a little blurry, that’s because – except for the last one – they’re displayed at one physical pixel per logical pixel. This is blurry in its own right, but the icons are blurrier still because I made them at fairly low quality.)

My usual assumptions are than regular images have suffices ‘@0’, ‘@1’, ‘@2’, ‘@3’, ..., with linear sizes in the ratios 7:10:14:20: ... . I assume that icons have suffix ‘@i’ and are 52×52, and that high resolution thumbnails, when present, have twice the linear pixel count of standard resolution. I assume that thumbnails have suffix ‘@t’ and their high-resolution counterparts have ‘@h’. (I only generate a single icon, though you can have as many as you want.)

I am flexible about the size of thumbnails. My own are two fifths the size of my default ‘@0’ image, but I can see the appeal of square thumbnails (eg. 160×160).

Suitable default sets of image sizes may therefore be these:
     @t   @0   @1   @2   @3  ...
7×5196×140490×350700×500980×700 1400×1000...
4×3180×135420×315600×450840×630 1200×900...

I am never entirely sure what range of screen resolutions to allow for. The pix.js software is perfectly general, and allows you to make provision for a wide range of resolutions or to supply images which are tuned only for a small set of sizes; but neither my practice nor the software on this page is equally general.

The regular images are not a problem. They make allowance for both screen size and pixel resolution. Once you’ve decided how large to make your largest image, everything else follows.

For icons (and high-resolution thumbnails when needed) my practice is to provide images which are efficient on single-resolution terminals and make full use of double resolution. The pixel resolution on mobile phones goes up to 4× and is often fractional. I do not take full advantage of these screens. I can take comfort from the fact that routemaster, in particular, is targeted at laptops/desktops.

On the other hand, it would be nice to have photo icons in a navigation tool for mobile phones, and the higher resolutions would probably bring some benefit (though I suspect that 4× is more of a sales gimmick). I think I’ll wait and see how the market settles down.

The pixcompact program (as distinct from the pixcompact.sh script) regenerates most versions of an image, discarding the exif. Its argument is the name of the thumbnail, eg. ‘stem@t.jpg’. It operation is as follows. It reads in all the regular versions. The largest is left unchanged; the remainder are regenerated by resampling starting from the largest.

If a hi-res thumbnail (‘@h’)is needed and exists, then it will be written out without exif; the normal thumbnail will be regenerated from it.

If a hi-res thumbnail is needed and does not exist, then it will be generated from a regular image and written out; the normal thumbnail will be regenerated from it.

If a hi-res thumbnail is not needed, then the normal thumbnail will be regenerated from a suitable regular image.

The behaviour is a little different if the thumbnail belongs to a video (detected by the presence of a ‘.mp4’ file). In this case the poster is compacted. If a hi-res thumbnail exists, it is saved at its own size and rescaled to replace the standard thumbnail; if not, the hi-res thumbnail is rescaled from the poster and again to give the standard thumbnail.

pixrewrite reads in and overwrites each file supplied in its argument list. So you can type

pixrewrite bumpypath@2.jpg

to update a single file, or

pixrewrite *.jpg

to update all jpgs in a directory. You may supply a quality as a pair of successive arguments, the first of which is “-q” and the second of which is the quality. The quality applies to all subsequent files in the argument list until it is overridden. Hence

pixrewrite -q 60 *@t.jpg -q 50 *@h.jpg

updates all ‘@t’ files at quality 60 and all ‘@h’ at quality 50.

pixrewrite provides an alternative way of performing the task performed by pixcompact, but the latter is preferred. Firstly, you want to keep the exif of large images; secondly pixcompact is able to regenerate images by compressing larger versions, whereas pixrewrite has to compress the file it is overwriting, which leads to an accumulation of compression errors (which probably doesn’t matter much in practice).

(Again referring to an executable program rather than a script of a similar name.) pixdownscale takes the largest regular image of a set (eg. ‘bumpypath@5.jpg’) and generates from it all the smaller versions (including one or two thumbnails and an icon).

An example call is

pixdownscale bumpypath@5.jpg 200 150

where the first argument is the file to start with and the other two are the dimensions of a thumbnail (it doesn’t matter which order they come in: the larger will be matched with the larger image dimension).

Therefore one way to produce an image gallery is to work through the photos one by one, editing a version to use as the largest size (eg. in Photoshop) and then running pixdownscale. I prefer to work in small batches, but this may be because I have to share the task between different computers.

The scripts perform in bulk the tasks performed for individual images by the executables.

pixicon.sh will generate a 52×52 ‘@i’ icon corresponding to every thumbnail for which such an icon does not already exist.

pixcompact.sh will regenerate compact versions of all but the largest version of every image in your current directory.

pixdownscale.sh invokes pixdownscale for every image for which the largest regular size is present in your current directory and the second-largest regular size is absent. Thus if you produce a few ‘@5’ files, it will generate the corresponding ‘@4’, ..., ‘@0’, ‘@t’, etc. If you produce a few more ‘@5’ files, then it will work through them without going back over the ones you’ve already processed.

The largest regular size should be passed as an argument, and may optionally be followed by the dimensions of a thumbnail (in any order), eg.

pixdownscale.sh 5 196 140

If thumbnail dimensions are omitted, 196×140 is assumed.

rescale resamples an image (stored as a pixel matrix) to a new size. It uses ‘weighted average downscaling’, which is fine for its purpose. (In particular, it’s exact if you’re downscaling by an integer ratio, and very accurate if the raio is large but non-integral.) Bicubic interpolation is considered better for upsampling, but it is not as mathematically sound as it is complicated.

readjpg and tinyreadjpg provide convenient interfaces to jpeg I/O libraries. Since the interfaces are calling-sequence identical, you can swap between libraries without changing your own code.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software (excluding nanojpeg.c and jpg.c, which are subject to their respective authors’ release conditions), to deal in it without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or to sell copies of the software, and to permit persons to whom the software is furnished to do the same.

The software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the author (Colin Champion) be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other diealings in the software.

#include "readjpg.h"
uchar *rescale(uchar *img,int W,int H,int w,int h,int ncol) ;

int main(int argc,char **argv)
{ if(argc<2) 
  { printf("usage: %s infile [outfile] [quality] [newwidth] [newheight] or\n"
           "       %s infile [outfile] [quality] /downscalefactor\n"
           "                    outfile defaults to infile\n"
           "                    quality defaults to 60\n"
           "                    new width/height default to old\n",
           argv[0],argv[0]) ; 
    return 0 ; 
  }
  int k ; 
  image img = readjpg(argv[1]) , omg = img ; 
  if(img.w<=0) throw up("%s cannot be found",argv[1]) ; 

  if(argc>4&&argv[4][0]=='/') 
  { k = atoi(argv[4]+1) ; omg.w = img.w / k ; omg.h = img.h / k ; }
  else
  { if(argc>4&&(k=atoi(argv[4]))>0) omg.w = k ; 
    if(argc>5&&(k=atoi(argv[5]))>0) omg.h = k ; 
  }

  if(omg.w!=img.w||omg.h!=img.h)
  { omg.u = rescale(img.u,img.w,img.h,omg.w,omg.h,img.ncol) ; free(img.u) ; }

  writejpg(omg,argc>2?argv[2]:argv[1],argc>3?atoi(argv[3]):60) ; 
}

#include "readjpg.h"

int main(int argc,char **argv)
{ if(argc<2) 
  { printf("usage: %s file1 file2 file3 ... \n",argv[0]) ; return 0 ; }
  int i , iqual=60 ; 
  image img ; 

  for(i=1;i<argc;i++) 
    if(i<argc-1&&!strcmp(argv[i],"-q"))
  { iqual = atoi(argv[i+1]) ; 
    if(iqual<1||iqual>100) throw up("%s is a faulty quality",argv[i+1]) ; 
    i += 1 ; 
  }
    else
  { img = readjpg(argv[i]) ; 
    if(img.w<=0) printf("%s cannot be processed\n",argv[i]) ; 
    else writejpg(img,argv[i],iqual) ; 
  }
}

#include "readjpg.h"
uchar *rescale(uchar *img,int W,int H,int w,int h,int ncol) ;

int main(int argc,char **argv)
{ if(argc<2) { printf("usage: %s stem@t.jpg\n",argv[0]) ; return 0 ; }
  image img = readjpg(argv[1]) , reg[10] , omg ;
  uchar *u ; 
  FILE *ifl ; 

  char *oname=charvector(argv[1]) , *rname ;
  int i,j,max,k = strlen(oname) , isvid=0; 
  if(strcmp(oname+k-6,"@t.jpg")) 
    throw up("%s is not a recognised thumbnail filename",argv[1]) ; 
  if(img.w<=0) throw up("%s is not a valid thumbnail",argv[1]) ; 

  for(max=i=0;;max=i,i++) // read in regular images and find max scale
  { oname[k-5] = '0' + i ; 
    reg[i] = readjpg(oname) ; 
    if(reg[i].w<=0) break ; 
  }

  if(max<1) // no '@0' file - perhaps it's a video
  { rname = charvector(k) ; 
    strncpy(rname,oname,k-6) ; 
    strcpy(rname+k-6,".mp4") ; 
    ifl = fopen(rname,"rb") ; 
    if(!ifl) throw up("no regular images corresponding to %s",argv[1]) ; 
    fclose(ifl) ;
    strcpy(rname+k-6,".jpg") ; 
    reg[0] = readjpg(rname) ; 
    if(reg[0].w<=0) throw up("no regular images corresponding to %s",argv[1]) ; 
    writejpg(reg[0],rname,60) ; 
    isvid = 1 ; 
    max = 0 ; 
  }
  else for(i=max-1;i>=0;i--) // regenerate all reg images smaller than the max
  { free(reg[i].u) ; 
    if(i==max-1) j = i+1 ; else j = i+2 ; 
    reg[i].u = 
      rescale(reg[j].u,reg[j].w,reg[j].h,reg[i].w,reg[i].h,reg[i].ncol) ; 
    if(j==i+2) free(reg[j].u) ; 
    oname[k-5] = '0' + i ; 
    writejpg(reg[i],oname,60) ; 
  }
  
  if(isvid||img.w*reg[0].h!=img.h*reg[0].w) // if we need a hi-res thumbnail
  { oname[k-5] = 'h' ; 
    omg = readjpg(oname) ;
    if(omg.w<0) 
    { omg.w = 2*img.w ; 
      omg.h = 2*img.h ; 
      omg.ncol = img.ncol ; 
      if(max>0) i = 1 ; else i = 0 ; 
      omg.u = rescale(reg[i].u,reg[i].w,reg[i].h,omg.w,omg.h,omg.ncol) ; 
    }
    writejpg(omg,oname,60) ; 
    free(img.u) ; 
    img.u = rescale(omg.u,omg.w,omg.h,img.w,img.h,img.ncol) ; 
  }
  writejpg(img,argv[1],60) ; 
}

#include "readjpg.h"
uchar *rescale(uchar *img,int W,int H,int w,int h,int ncol) ;

int main(int argc,char **argv)
{ if(argc!=2&&argc!=4) 
  { printf("usage: %s filename@n.jpg [thw] [thh]\n",argv[0]) ; return 0 ; }
  char *hi=charvector(argv[1]) ;
  uchar *u,*v ; 
  int i , k = strlen(hi) , scale[10] , max , thw , thh , w , h , x ; 

  max = hi[k-5] - '0' ; 
  if(strcmp(hi+k-4,".jpg")||hi[k-6]!='@'||max<0||max>9)
    throw up("%s is not a recognised image",argv[1]) ; 
  if(argc==2) { thw = 196 ; thh = 140 ; }
  else 
  { thw = atoi(argv[2]) ; 
    thh = atoi(argv[3]) ; 
    if(thh>thw) swap(thw,thh) ; 
  }

  for(scale[0]=7,scale[1]=10,i=2;i<=max;i++) scale[i] = 2 * scale[i-2] ;
  image img = readjpg(argv[1]) , jmg = img , *p ; 
  if(img.w<=0) throw up("unable to get an image from %s",argv[1]) ; 

  // check all sizes are integral
  for(x=scale[max],i=max-1;i>=0;i--) 
  { hi[k-5] = '0' + i ; 
    w = ( img.w * scale[i] ) / x ; 
    h = ( img.h * scale[i] ) / x ; 
    if(w*x!=img.w*scale[i]||h*x!=img.h*scale[i]) 
      throw up("%s [%d,%d] gives a non-integral size for %s",
               argv[1],img.w,img.h,hi) ; 
    if(i==max-1) { jmg.w = w ; jmg.h = h ; }
  }
  printf("%s: %dx%d\n",argv[1],img.w,img.h) ; 

  // generate regular images
  for(i=max-1;i>=0;i--) 
  { hi[k-5] = '0' + i ; 
    p = &jmg ; 
    if(i==max-1) jmg.u = rescale(img.u,img.w,img.h,jmg.w,jmg.h,img.ncol) ;  
    else 
    { if((i&1)==(max&1)) p = &img ; 
      u = rescale(p->u,p->w,p->h,p->w/2,p->h/2,p->ncol) ; 
      free(p->u) ; 
      p[0] = image(p->w/2,p->h/2,p->ncol,u) ; 
    }
    writejpg(p[0],hi,60) ;
  }

  // generate the thumb
  if(img.h>img.w) swap(thw,thh) ; 
  if(max==0||img.w<jmg.w) p = &img ; else p = &jmg ; 
  u = rescale(p->u,p->w,p->h,thw,thh,p->ncol) ;
  hi[k-5] = 't' ;
  writejpg(image(thw,thh,p->ncol,u),hi,60) ;

  // generate the icon
  v = rescale(u,thw,thh,52,52,p->ncol) ;
  hi[k-5] = 'i' ;
  writejpg(image(52,52,p->ncol,v),hi,60) ;
  free(u,v) ; 

  // maybe generate the hithumb
  if(p->w*thh!=p->h*thw)
  { if(max>0&&img.w<jmg.w) p = &jmg ; else p = &img ; 
    u = rescale(p->u,p->w,p->h,2*thw,2*thh,p->ncol) ;
    hi[k-5] = 'h' ;
    writejpg(image(2*thw,2*thh,p->ncol,u),hi,60) ;
    free(u) ; 
  }
}

• compressmap     • compress     • rescale

#include "memory.h"
#define uchar unsigned char
struct mapitem { int i0,i1 ; double w0,w1 ; } ;
genvector(uchar,ucharvector) ;
genvector(mapitem,mapvector) ;

/* -------------------------------------------------------------------------- */

static mapitem *compressmap(int N,int n)
{ int i ; 
  double q ;
  mapitem m , *maplist = mapvector(n) ; 
  for(i=0;i<n;i++)
  { if((i*N)%n==0) { m.i0 = (i*N)/n ; m.w0 = 1 ; }
    else { q = (i*N)/(double) n ; m.i0 = (int) q ; m.w0 = 1 - (q-m.i0) ; }
    if(((i+1)*N)%n==0) { m.i1 = ((i+1)*N)/n - 1 ; m.w1 = 1 ; }
    else { q = ((i+1)*N)/(double) n ; m.i1 = (int) q ; m.w1 = q - m.i1 ; }
    if(m.i1==m.i0) { m.w0 -= 1-m.w1 ; m.w1 = 0 ; }
    maplist[i] = m ; 
  }
  return maplist ;
}
/* -------------------------------------------------------------------------- */

static void compress(uchar *x,int N,uchar *y,int n,mapitem *map)
{ int i,j ; 
  mapitem m ; 
  double q,r=n/(double)N ; 
  for(i=0;i<n;i++) 
  { for(m=map[i],q=x[m.i0]*m.w0+x[m.i1]*m.w1,j=m.i0+1;j<m.i1;j++) q += x[j] ; 
    y[i] = q*r ; 
  }
}
/* -------------------------------------------------------------------------- */

uchar *rescale(uchar *img,int W,int H,int w,int h,int ncol)
{ int i,j,k,col,n,offs ; 
  mapitem *map ; 
  uchar *img2 = ucharvector(ncol*h*w) , *img1 = ucharvector(ncol*H*w) ; 
  uchar *xa = ucharvector(H>W?H:W) , *xb = ucharvector(h>w?h:w) ;

  for(col=0;col<ncol;col++) 
  { // downsample width
    if(H*w<h*W) n = (int) (0.5+(H*w)/(double) h) ; else n = W ; 
    map = compressmap(n,w) ; 
    offs = (W-n)/2 ;
    for(i=0;i<H;i++) 
    { k = col + ncol * ( offs + W*i ) ;
      for(j=0;j<n;j++) xa[j] = img[k+ncol*j] ; 
      compress(xa,n,xb,w,map) ; 
      k = col + ncol*i*w ;
      for(j=0;j<w;j++) img1[k+ncol*j] = xb[j] ; 
    }
    free(map) ; 
    // downsample height
    if(W*h<w*H) n = (int) (0.5+(W*h)/(double) w) ; else n = H ; 
    map = compressmap(n,h) ; 
    offs = (H-n)/2 ;
    for(j=0;j<w;j++) 
    { k = col + ncol * ( j + w*offs ) ; 
      for(i=0;i<n;i++) xa[i] = img1[ncol*w*i+k] ; 
      compress(xa,n,xb,h,map) ; 
      k = col + ncol*j ; 
      for(i=0;i<h;i++) img2[ncol*w*i+k] = xb[i] ; 
    }
    free(map) ; 
  }
  free(img1,xa,xb) ; 
  return img2 ;
}

• readjpg     • writejpg

#include "readjpg.h"

// nj_result_t: Result codes for njDecode().
typedef enum _nj_result {
    NJ_OK = 0,        // no error, decoding successful
    NJ_NO_JPEG,       // not a JPEG file
    NJ_UNSUPPORTED,   // unsupported format
    NJ_OUT_OF_MEM,    // out of memory
    NJ_INTERNAL_ERR,  // internal error
    NJ_SYNTAX_ERROR,  // syntax error
    __NJ_FINISHED,    // used internally, will never be reported
} nj_result_t;

void njInit(void);
nj_result_t njDecode(const void* jpeg, const int size);
int njGetWidth(void);
int njGetHeight(void);
int njIsColor(void);
unsigned char* njGetImage(void);
int njGetImageSize(void);
void njDone(void);

extern bool jo_write_jpg(const char*,const void*,int,int,int,int) ;

image readjpg(char *x)
{ int i,n ;
  FILE *ifl=fopen(x,"rb") ;
  char *buf,*whinge ; 
  unsigned char *u ; 
  image img ; 
  if(!ifl) return img ; 

  fseek(ifl,0,SEEK_END) ;
  n = (int) ftell(ifl) ;
  buf = charvector(n) ;
  fseek(ifl,0,SEEK_SET) ;
  n = fread(buf,1,n,ifl) ;
  fclose(ifl) ; 

  njInit() ;
  nj_result_t resp = njDecode(buf,n) ;
  if(resp)
  { if(resp==NJ_NO_JPEG) whinge = "not jpeg" ;
    else if(resp==NJ_UNSUPPORTED) whinge = "not supported" ;
    else if(resp==NJ_OUT_OF_MEM) whinge = "out of memory" ;
    else if(resp==NJ_INTERNAL_ERR) whinge = "internal error" ;
    else if(resp==NJ_SYNTAX_ERROR) whinge = "syntax error" ;
    else whinge = "unspecified error" ;
    throw up("Error \"%s\" decoding %s",whinge,x) ;
  }
  free(buf) ; 
  fclose(ifl) ; 
  img = image(njGetWidth(),njGetHeight(),1+2*njIsColor(),0) ; 
  n = img.w * img.h * img.ncol ;
  img.u = ucharvector(n) ; 
  u = njGetImage() ;
  for(i=0;i<n;i++) img.u[i] = u[i] ; 
  njDone() ; 
  return img ;
}
void writejpg(image img,char *filename,int iqual)
{ jo_write_jpg(filename,img.u,img.w,img.h,img.ncol,iqual) ; }

• my_error_exit     • readjpg     • writejpg

#include "readjpg.h"
#include "jpeglib.h" // /opt/homebrew/opt/jpeg/include/jpeglib.h
#include <setjmp.h>

struct my_error_mgr
{ struct jpeg_error_mgr pub;    /* "public" fields */
  jmp_buf setjmp_buffer;    /* for return to caller */
} ;
typedef struct my_error_mgr * my_error_ptr;

METHODDEF(void) my_error_exit (j_common_ptr cinfo)
{ /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
  my_error_ptr myerr = (my_error_ptr) cinfo->err;
  (*cinfo->err->output_message) (cinfo);
  longjmp(myerr->setjmp_buffer, 1);
}
image readjpg(char *filename)
{ image img ; 
  img.w = img.h = img.ncol = -1 ; 
  img.u = 0 ; 
  struct jpeg_decompress_struct cinfo;
  struct my_error_mgr jerr;
  FILE * infile;        /* source file */
  JSAMPARRAY buffer;        /* Output row buffer */
  int row_stride;        /* physical row width in output buffer */

  if(!(infile=fopen(filename,"rb"))) return img ; 

  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = my_error_exit;
  /* Establish the setjmp return context for my_error_exit to use. */
  if (setjmp(jerr.setjmp_buffer)) 
  { jpeg_destroy_decompress(&cinfo);
    fclose(infile);
    return img;
  }

  /* Now we can initialize the JPEG decompression object. */
  jpeg_create_decompress(&cinfo);
  jpeg_stdio_src(&cinfo, infile);
  (void) jpeg_read_header(&cinfo, TRUE);
  (void) jpeg_start_decompress(&cinfo);

  img.w = cinfo.output_width ;
  img.h = cinfo.output_height ;
  img.ncol = cinfo.output_components ;
  img.u = (unsigned char *) malloc(img.h*img.w*img.ncol) ; 

  row_stride = cinfo.output_width * cinfo.output_components;
  /* Make a one-row-high sample array that will go away when done with image */
  buffer = (*cinfo.mem->alloc_sarray)
        ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

  while (cinfo.output_scanline < cinfo.output_height) {
    (void) jpeg_read_scanlines(&cinfo, buffer, 1);
    memcpy(img.u+row_stride*(cinfo.output_scanline-1),buffer[0],row_stride);
  }

  (void) jpeg_finish_decompress(&cinfo);
  jpeg_destroy_decompress(&cinfo);
  fclose(infile);
  return img;
}

void writejpg(image img,char * filename,int quality)
{ struct jpeg_compress_struct cinfo;
  struct jpeg_error_mgr jerr;
  FILE * outfile;        /* target file */
  JSAMPROW buffer;    /* pointer to JSAMPLE row[s] */
  int row_stride;        /* physical row width in image buffer */

  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_compress(&cinfo);

  if(!(outfile=fopen(filename,"wb"))) throw up("unable to write %s",filename) ; 
  jpeg_stdio_dest(&cinfo, outfile);

  cinfo.image_width = img.w;          /* image width and height, in pixels */
  cinfo.image_height = img.h;
  cinfo.input_components = img.ncol;  /* # of color components per pixel */
  cinfo.in_color_space = JCS_RGB;     /* colorspace of input image */
  jpeg_set_defaults(&cinfo);
  /* Now you can set any non-default parameters you wish to.
   * Here we just illustrate the use of quality (quantization table) scaling:
   */
  jpeg_set_quality(&cinfo, quality, TRUE); /* limit to baseline-JPEG values */
  jpeg_start_compress(&cinfo, TRUE);

  row_stride = img.w * img.ncol;      /* JSAMPLEs per row in image_buffer */

  while (cinfo.next_scanline < cinfo.image_height) {
    /* jpeg_write_scanlines expects an array of pointers to scanlines.
     * Here the array is only one element long, but you could pass
     * more than one scanline at a time if that's more convenient.
     */
    buffer = & img.u[cinfo.next_scanline * row_stride];
    (void) jpeg_write_scanlines(&cinfo,&buffer,1);
  }

  jpeg_finish_compress(&cinfo);
  fclose(outfile);
  jpeg_destroy_compress(&cinfo);
}

• main     • njFillMem     • njCopyMem     • njClip     • njRowIDCT     • njColIDCT     • njShowBits     • njSkipBits     • njGetBits     • njByteAlign     • njSkip     • njDecode16     • njDecodeLength     • njSkipMarker     • njDecodeSOF     • njDecodeDHT     • njDecodeDQT     • njDecodeDRI     • njGetVLC     • njDecodeBlock     • njDecodeScan     • njUpsampleH     • njUpsampleV     • njUpsample     • njConvert     • njInit     • njDone     • njDecode     • njGetWidth     • njGetHeight     • njIsColor     • njGetImage     • njGetImageSize

// NanoJPEG -- KeyJ's Tiny Baseline JPEG Decoder
// version 1.3.5 (2016-11-14)
// Copyright (c) 2009-2016 Martin J. Fiedler <martin.fiedler@gmx.net>
// published under the terms of the MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.


///////////////////////////////////////////////////////////////////////////////
// DOCUMENTATION SECTION                                                     //
// read this if you want to know what this is all about                      //
///////////////////////////////////////////////////////////////////////////////

// INTRODUCTION
// ============
//
// This is a minimal decoder for baseline JPEG images. It accepts memory dumps
// of JPEG files as input and generates either 8-bit grayscale or packed 24-bit
// RGB images as output. It does not parse JFIF or Exif headers; all JPEG files
// are assumed to be either grayscale or YCbCr. CMYK or other color spaces are
// not supported. All YCbCr subsampling schemes with power-of-two ratios are
// supported, as are restart intervals. Progressive or lossless JPEG is not
// supported.
// Summed up, NanoJPEG should be able to decode all images from digital cameras
// and most common forms of other non-progressive JPEG images.
// The decoder is not optimized for speed, it's optimized for simplicity and
// small code. Image quality should be at a reasonable level. A bicubic chroma
// upsampling filter ensures that subsampled YCbCr images are rendered in
// decent quality. The decoder is not meant to deal with broken JPEG files in
// a graceful manner; if anything is wrong with the bitstream, decoding will
// simply fail.
// The code should work with every modern C compiler without problems and
// should not emit any warnings. It uses only (at least) 32-bit integer
// arithmetic and is supposed to be endianness independent and 64-bit clean.
// However, it is not thread-safe.


// COMPILE-TIME CONFIGURATION
// ==========================
//
// The following aspects of NanoJPEG can be controlled with preprocessor
// defines:
//
// _NJ_EXAMPLE_PROGRAM     = Compile a main() function with an example
//                           program.
// _NJ_INCLUDE_HEADER_ONLY = Don't compile anything, just act as a header
//                           file for NanoJPEG. Example:
//                               #define _NJ_INCLUDE_HEADER_ONLY
//                               #include "nanojpeg.c"
//                               int main(void) {
//                                   njInit();
//                                   // your code here
//                                   njDone();
//                               }
// NJ_USE_LIBC=1           = Use the malloc(), free(), memset() and memcpy()
//                           functions from the standard C library (default).
// NJ_USE_LIBC=0           = Don't use the standard C library. In this mode,
//                           external functions njAlloc(), njFreeMem(),
//                           njFillMem() and njCopyMem() need to be defined
//                           and implemented somewhere.
// NJ_USE_WIN32=0          = Normal mode (default).
// NJ_USE_WIN32=1          = If compiling with MSVC for Win32 and
//                           NJ_USE_LIBC=0, NanoJPEG will use its own
//                           implementations of the required C library
//                           functions (default if compiling with MSVC and
//                           NJ_USE_LIBC=0).
// NJ_CHROMA_FILTER=1      = Use the bicubic chroma upsampling filter
//                           (default).
// NJ_CHROMA_FILTER=0      = Use simple pixel repetition for chroma upsampling
//                           (bad quality, but faster and less code).


// API
// ===
//
// For API documentation, read the "header section" below.


// EXAMPLE
// =======
//
// A few pages below, you can find an example program that uses NanoJPEG to
// convert JPEG files into PGM or PPM. To compile it, use something like
//     gcc -O3 -D_NJ_EXAMPLE_PROGRAM -o nanojpeg nanojpeg.c
// You may also add -std=c99 -Wall -Wextra -pedantic -Werror, if you want :)
// The only thing you might need is -Wno-shift-negative-value, because this
// code relies on the target machine using two's complement arithmetic, but
// the C standard does not, even though *any* practically useful machine
// nowadays uses two's complement.


///////////////////////////////////////////////////////////////////////////////
// HEADER SECTION                                                            //
// copy and pase this into nanojpeg.h if you want                            //
///////////////////////////////////////////////////////////////////////////////

#ifndef _NANOJPEG_H
#define _NANOJPEG_H

// nj_result_t: Result codes for njDecode().
typedef enum _nj_result {
    NJ_OK = 0,        // no error, decoding successful
    NJ_NO_JPEG,       // not a JPEG file
    NJ_UNSUPPORTED,   // unsupported format
    NJ_OUT_OF_MEM,    // out of memory
    NJ_INTERNAL_ERR,  // internal error
    NJ_SYNTAX_ERROR,  // syntax error
    __NJ_FINISHED,    // used internally, will never be reported
} nj_result_t;

// njInit: Initialize NanoJPEG.
// For safety reasons, this should be called at least one time before using
// using any of the other NanoJPEG functions.
void njInit(void);

// njDecode: Decode a JPEG image.
// Decodes a memory dump of a JPEG file into internal buffers.
// Parameters:
//   jpeg = The pointer to the memory dump.
//   size = The size of the JPEG file.
// Return value: The error code in case of failure, or NJ_OK (zero) on success.
nj_result_t njDecode(const void* jpeg, const int size);

// njGetWidth: Return the width (in pixels) of the most recently decoded
// image. If njDecode() failed, the result of njGetWidth() is undefined.
int njGetWidth(void);

// njGetHeight: Return the height (in pixels) of the most recently decoded
// image. If njDecode() failed, the result of njGetHeight() is undefined.
int njGetHeight(void);

// njIsColor: Return 1 if the most recently decoded image is a color image
// (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result
// of njGetWidth() is undefined.
int njIsColor(void);

// njGetImage: Returns the decoded image data.
// Returns a pointer to the most recently image. The memory layout it byte-
// oriented, top-down, without any padding between lines. Pixels of color
// images will be stored as three consecutive bytes for the red, green and
// blue channels. This data format is thus compatible with the PGM or PPM
// file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8.
// If njDecode() failed, the result of njGetImage() is undefined.
unsigned char* njGetImage(void);

// njGetImageSize: Returns the size (in bytes) of the image data returned
// by njGetImage(). If njDecode() failed, the result of njGetImageSize() is
// undefined.
int njGetImageSize(void);

// njDone: Uninitialize NanoJPEG.
// Resets NanoJPEG's internal state and frees all memory that has been
// allocated at run-time by NanoJPEG. It is still possible to decode another
// image after a njDone() call.
void njDone(void);

#endif//_NANOJPEG_H


///////////////////////////////////////////////////////////////////////////////
// CONFIGURATION SECTION                                                     //
// adjust the default settings for the NJ_ defines here                      //
///////////////////////////////////////////////////////////////////////////////

#ifndef NJ_USE_LIBC
    #define NJ_USE_LIBC 1
#endif

#ifndef NJ_USE_WIN32
  #ifdef _MSC_VER
    #define NJ_USE_WIN32 (!NJ_USE_LIBC)
  #else
    #define NJ_USE_WIN32 0
  #endif
#endif

#ifndef NJ_CHROMA_FILTER
    #define NJ_CHROMA_FILTER 1
#endif


///////////////////////////////////////////////////////////////////////////////
// EXAMPLE PROGRAM                                                           //
// just define _NJ_EXAMPLE_PROGRAM to compile this (requires NJ_USE_LIBC)    //
///////////////////////////////////////////////////////////////////////////////

#ifdef  _NJ_EXAMPLE_PROGRAM

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char* argv[]) {
    int size;
    char *buf;
    FILE *f;

    if (argc < 2) {
        printf("Usage: %s <input.jpg> [<output.ppm>]\n", argv[0]);
        return 2;
    }
    f = fopen(argv[1], "rb");
    if (!f) {
        printf("Error opening the input file.\n");
        return 1;
    }
    fseek(f, 0, SEEK_END);
    size = (int) ftell(f);
    buf = (char*) malloc(size);
    fseek(f, 0, SEEK_SET);
    size = (int) fread(buf, 1, size, f);
    fclose(f);

    njInit();
    if (njDecode(buf, size)) {
        free((void*)buf);
        printf("Error decoding the input file.\n");
        return 1;
    }
    free((void*)buf);

    f = fopen((argc > 2) ? argv[2] : (njIsColor() ? "nanojpeg_out.ppm" : "nanojpeg_out.pgm"), "wb");
    if (!f) {
        printf("Error opening the output file.\n");
        return 1;
    }
    fprintf(f, "P%d\n%d %d\n255\n", njIsColor() ? 6 : 5, njGetWidth(), njGetHeight());
    fwrite(njGetImage(), 1, njGetImageSize(), f);
    fclose(f);
    njDone();
    return 0;
}

#endif


///////////////////////////////////////////////////////////////////////////////
// IMPLEMENTATION SECTION                                                    //
// you may stop reading here                                                 //
///////////////////////////////////////////////////////////////////////////////

#ifndef _NJ_INCLUDE_HEADER_ONLY

#ifdef _MSC_VER
    #define NJ_INLINE static __inline
    #define NJ_FORCE_INLINE static __forceinline
#else
    #define NJ_INLINE static inline
    #define NJ_FORCE_INLINE static inline
#endif

#if NJ_USE_LIBC
    #include <stdlib.h>
    #include <string.h>
    #define njAllocMem malloc
    #define njFreeMem  free
    #define njFillMem  memset
    #define njCopyMem  memcpy
#elif NJ_USE_WIN32
    #include <windows.h>
    #define njAllocMem(size) ((void*) LocalAlloc(LMEM_FIXED, (SIZE_T)(size)))
    #define njFreeMem(block) ((void) LocalFree((HLOCAL) block))
    NJ_INLINE void njFillMem(void* block, unsigned char value, int count) { __asm {
        mov edi, block
        mov al, value
        mov ecx, count
        rep stosb
    } }
    NJ_INLINE void njCopyMem(void* dest, const void* src, int count) { __asm {
        mov edi, dest
        mov esi, src
        mov ecx, count
        rep movsb
    } }
#else
    extern void* njAllocMem(int size);
    extern void njFreeMem(void* block);
    extern void njFillMem(void* block, unsigned char byte, int size);
    extern void njCopyMem(void* dest, const void* src, int size);
#endif

typedef struct _nj_code {
    unsigned char bits, code;
} nj_vlc_code_t;

typedef struct _nj_cmp {
    int cid;
    int ssx, ssy;
    int width, height;
    int stride;
    int qtsel;
    int actabsel, dctabsel;
    int dcpred;
    unsigned char *pixels;
} nj_component_t;

typedef struct _nj_ctx {
    nj_result_t error;
    const unsigned char *pos;
    int size;
    int length;
    int width, height;
    int mbwidth, mbheight;
    int mbsizex, mbsizey;
    int ncomp;
    nj_component_t comp[3];
    int qtused, qtavail;
    unsigned char qtab[4][64];
    nj_vlc_code_t vlctab[4][65536];
    int buf, bufbits;
    int block[64];
    int rstinterval;
    unsigned char *rgb;
} nj_context_t;

static nj_context_t nj;

static const char njZZ[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18,
11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35,
42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45,
38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 };

NJ_FORCE_INLINE unsigned char njClip(const int x) {
    return (x < 0) ? 0 : ((x > 0xFF) ? 0xFF : (unsigned char) x);
}

#define W1 2841
#define W2 2676
#define W3 2408
#define W5 1609
#define W6 1108
#define W7 565

NJ_INLINE void njRowIDCT(int* blk) {
    int x0, x1, x2, x3, x4, x5, x6, x7, x8;
    if (!((x1 = blk[4] << 11)
        | (x2 = blk[6])
        | (x3 = blk[2])
        | (x4 = blk[1])
        | (x5 = blk[7])
        | (x6 = blk[5])
        | (x7 = blk[3])))
    {
        blk[0] = blk[1] = blk[2] = blk[3] = blk[4] = blk[5] = blk[6] = blk[7] = blk[0] << 3;
        return;
    }
    x0 = (blk[0] << 11) + 128;
    x8 = W7 * (x4 + x5);
    x4 = x8 + (W1 - W7) * x4;
    x5 = x8 - (W1 + W7) * x5;
    x8 = W3 * (x6 + x7);
    x6 = x8 - (W3 - W5) * x6;
    x7 = x8 - (W3 + W5) * x7;
    x8 = x0 + x1;
    x0 -= x1;
    x1 = W6 * (x3 + x2);
    x2 = x1 - (W2 + W6) * x2;
    x3 = x1 + (W2 - W6) * x3;
    x1 = x4 + x6;
    x4 -= x6;
    x6 = x5 + x7;
    x5 -= x7;
    x7 = x8 + x3;
    x8 -= x3;
    x3 = x0 + x2;
    x0 -= x2;
    x2 = (181 * (x4 + x5) + 128) >> 8;
    x4 = (181 * (x4 - x5) + 128) >> 8;
    blk[0] = (x7 + x1) >> 8;
    blk[1] = (x3 + x2) >> 8;
    blk[2] = (x0 + x4) >> 8;
    blk[3] = (x8 + x6) >> 8;
    blk[4] = (x8 - x6) >> 8;
    blk[5] = (x0 - x4) >> 8;
    blk[6] = (x3 - x2) >> 8;
    blk[7] = (x7 - x1) >> 8;
}

NJ_INLINE void njColIDCT(const int* blk, unsigned char *out, int stride) {
    int x0, x1, x2, x3, x4, x5, x6, x7, x8;
    if (!((x1 = blk[8*4] << 8)
        | (x2 = blk[8*6])
        | (x3 = blk[8*2])
        | (x4 = blk[8*1])
        | (x5 = blk[8*7])
        | (x6 = blk[8*5])
        | (x7 = blk[8*3])))
    {
        x1 = njClip(((blk[0] + 32) >> 6) + 128);
        for (x0 = 8;  x0;  --x0) {
            *out = (unsigned char) x1;
            out += stride;
        }
        return;
    }
    x0 = (blk[0] << 8) + 8192;
    x8 = W7 * (x4 + x5) + 4;
    x4 = (x8 + (W1 - W7) * x4) >> 3;
    x5 = (x8 - (W1 + W7) * x5) >> 3;
    x8 = W3 * (x6 + x7) + 4;
    x6 = (x8 - (W3 - W5) * x6) >> 3;
    x7 = (x8 - (W3 + W5) * x7) >> 3;
    x8 = x0 + x1;
    x0 -= x1;
    x1 = W6 * (x3 + x2) + 4;
    x2 = (x1 - (W2 + W6) * x2) >> 3;
    x3 = (x1 + (W2 - W6) * x3) >> 3;
    x1 = x4 + x6;
    x4 -= x6;
    x6 = x5 + x7;
    x5 -= x7;
    x7 = x8 + x3;
    x8 -= x3;
    x3 = x0 + x2;
    x0 -= x2;
    x2 = (181 * (x4 + x5) + 128) >> 8;
    x4 = (181 * (x4 - x5) + 128) >> 8;
    *out = njClip(((x7 + x1) >> 14) + 128);  out += stride;
    *out = njClip(((x3 + x2) >> 14) + 128);  out += stride;
    *out = njClip(((x0 + x4) >> 14) + 128);  out += stride;
    *out = njClip(((x8 + x6) >> 14) + 128);  out += stride;
    *out = njClip(((x8 - x6) >> 14) + 128);  out += stride;
    *out = njClip(((x0 - x4) >> 14) + 128);  out += stride;
    *out = njClip(((x3 - x2) >> 14) + 128);  out += stride;
    *out = njClip(((x7 - x1) >> 14) + 128);
}

#define njThrow(e) do { nj.error = e; return; } while (0)
#define njCheckError() do { if (nj.error) return; } while (0)

static int njShowBits(int bits) {
    unsigned char newbyte;
    if (!bits) return 0;
    while (nj.bufbits < bits) {
        if (nj.size <= 0) {
            nj.buf = (nj.buf << 8) | 0xFF;
            nj.bufbits += 8;
            continue;
        }
        newbyte = *nj.pos++;
        nj.size--;
        nj.bufbits += 8;
        nj.buf = (nj.buf << 8) | newbyte;
        if (newbyte == 0xFF) {
            if (nj.size) {
                unsigned char marker = *nj.pos++;
                nj.size--;
                switch (marker) {
                    case 0x00:
                    case 0xFF:
                        break;
                    case 0xD9: nj.size = 0; break;
                    default:
                        if ((marker & 0xF8) != 0xD0)
                            nj.error = NJ_SYNTAX_ERROR;
                        else {
                            nj.buf = (nj.buf << 8) | marker;
                            nj.bufbits += 8;
                        }
                }
            } else
                nj.error = NJ_SYNTAX_ERROR;
        }
    }
    return (nj.buf >> (nj.bufbits - bits)) & ((1 << bits) - 1);
}

NJ_INLINE void njSkipBits(int bits) {
    if (nj.bufbits < bits)
        (void) njShowBits(bits);
    nj.bufbits -= bits;
}

NJ_INLINE int njGetBits(int bits) {
    int res = njShowBits(bits);
    njSkipBits(bits);
    return res;
}

NJ_INLINE void njByteAlign(void) {
    nj.bufbits &= 0xF8;
}

static void njSkip(int count) {
    nj.pos += count;
    nj.size -= count;
    nj.length -= count;
    if (nj.size < 0) nj.error = NJ_SYNTAX_ERROR;
}

NJ_INLINE unsigned short njDecode16(const unsigned char *pos) {
    return (pos[0] << 8) | pos[1];
}

static void njDecodeLength(void) {
    if (nj.size < 2) njThrow(NJ_SYNTAX_ERROR);
    nj.length = njDecode16(nj.pos);
    if (nj.length > nj.size) njThrow(NJ_SYNTAX_ERROR);
    njSkip(2);
}

NJ_INLINE void njSkipMarker(void) {
    njDecodeLength();
    njSkip(nj.length);
}

NJ_INLINE void njDecodeSOF(void) {
    int i, ssxmax = 0, ssymax = 0;
    nj_component_t* c;
    njDecodeLength();
    njCheckError();
    if (nj.length < 9) njThrow(NJ_SYNTAX_ERROR);
    if (nj.pos[0] != 8) njThrow(NJ_UNSUPPORTED);
    nj.height = njDecode16(nj.pos+1);
    nj.width = njDecode16(nj.pos+3);
    if (!nj.width || !nj.height) njThrow(NJ_SYNTAX_ERROR);
    nj.ncomp = nj.pos[5];
    njSkip(6);
    switch (nj.ncomp) {
        case 1:
        case 3:
            break;
        default:
            njThrow(NJ_UNSUPPORTED);
    }
    if (nj.length < (nj.ncomp * 3)) njThrow(NJ_SYNTAX_ERROR);
    for (i = 0, c = nj.comp;  i < nj.ncomp;  ++i, ++c) {
        c->cid = nj.pos[0];
        if (!(c->ssx = nj.pos[1] >> 4)) njThrow(NJ_SYNTAX_ERROR);
        if (c->ssx & (c->ssx - 1)) njThrow(NJ_UNSUPPORTED);  // non-power of two
        if (!(c->ssy = nj.pos[1] & 15)) njThrow(NJ_SYNTAX_ERROR);
        if (c->ssy & (c->ssy - 1)) njThrow(NJ_UNSUPPORTED);  // non-power of two
        if ((c->qtsel = nj.pos[2]) & 0xFC) njThrow(NJ_SYNTAX_ERROR);
        njSkip(3);
        nj.qtused |= 1 << c->qtsel;
        if (c->ssx > ssxmax) ssxmax = c->ssx;
        if (c->ssy > ssymax) ssymax = c->ssy;
    }
    if (nj.ncomp == 1) {
        c = nj.comp;
        c->ssx = c->ssy = ssxmax = ssymax = 1;
    }
    nj.mbsizex = ssxmax << 3;
    nj.mbsizey = ssymax << 3;
    nj.mbwidth = (nj.width + nj.mbsizex - 1) / nj.mbsizex;
    nj.mbheight = (nj.height + nj.mbsizey - 1) / nj.mbsizey;
    for (i = 0, c = nj.comp;  i < nj.ncomp;  ++i, ++c) {
        c->width = (nj.width * c->ssx + ssxmax - 1) / ssxmax;
        c->height = (nj.height * c->ssy + ssymax - 1) / ssymax;
        c->stride = nj.mbwidth * c->ssx << 3;
        if (((c->width < 3) && (c->ssx != ssxmax)) || ((c->height < 3) && (c->ssy != ssymax))) njThrow(NJ_UNSUPPORTED);
        if (!(c->pixels = (unsigned char*) njAllocMem(c->stride * nj.mbheight * c->ssy << 3))) njThrow(NJ_OUT_OF_MEM);
    }
    if (nj.ncomp == 3) {
        nj.rgb = (unsigned char*) njAllocMem(nj.width * nj.height * nj.ncomp);
        if (!nj.rgb) njThrow(NJ_OUT_OF_MEM);
    }
    njSkip(nj.length);
}

NJ_INLINE void njDecodeDHT(void) {
    int codelen, currcnt, remain, spread, i, j;
    nj_vlc_code_t *vlc;
    static unsigned char counts[16];
    njDecodeLength();
    njCheckError();
    while (nj.length >= 17) {
        i = nj.pos[0];
        if (i & 0xEC) njThrow(NJ_SYNTAX_ERROR);
        if (i & 0x02) njThrow(NJ_UNSUPPORTED);
        i = (i | (i >> 3)) & 3;  // combined DC/AC + tableid value
        for (codelen = 1;  codelen <= 16;  ++codelen)
            counts[codelen - 1] = nj.pos[codelen];
        njSkip(17);
        vlc = &nj.vlctab[i][0];
        remain = spread = 65536;
        for (codelen = 1;  codelen <= 16;  ++codelen) {
            spread >>= 1;
            currcnt = counts[codelen - 1];
            if (!currcnt) continue;
            if (nj.length < currcnt) njThrow(NJ_SYNTAX_ERROR);
            remain -= currcnt << (16 - codelen);
            if (remain < 0) njThrow(NJ_SYNTAX_ERROR);
            for (i = 0;  i < currcnt;  ++i) {
                register unsigned char code = nj.pos[i];
                for (j = spread;  j;  --j) {
                    vlc->bits = (unsigned char) codelen;
                    vlc->code = code;
                    ++vlc;
                }
            }
            njSkip(currcnt);
        }
        while (remain--) {
            vlc->bits = 0;
            ++vlc;
        }
    }
    if (nj.length) njThrow(NJ_SYNTAX_ERROR);
}

NJ_INLINE void njDecodeDQT(void) {
    int i;
    unsigned char *t;
    njDecodeLength();
    njCheckError();
    while (nj.length >= 65) {
        i = nj.pos[0];
        if (i & 0xFC) njThrow(NJ_SYNTAX_ERROR);
        nj.qtavail |= 1 << i;
        t = &nj.qtab[i][0];
        for (i = 0;  i < 64;  ++i)
            t[i] = nj.pos[i + 1];
        njSkip(65);
    }
    if (nj.length) njThrow(NJ_SYNTAX_ERROR);
}

NJ_INLINE void njDecodeDRI(void) {
    njDecodeLength();
    njCheckError();
    if (nj.length < 2) njThrow(NJ_SYNTAX_ERROR);
    nj.rstinterval = njDecode16(nj.pos);
    njSkip(nj.length);
}

static int njGetVLC(nj_vlc_code_t* vlc, unsigned char* code) {
    int value = njShowBits(16);
    int bits = vlc[value].bits;
    if (!bits) { nj.error = NJ_SYNTAX_ERROR; return 0; }
    njSkipBits(bits);
    value = vlc[value].code;
    if (code) *code = (unsigned char) value;
    bits = value & 15;
    if (!bits) return 0;
    value = njGetBits(bits);
    if (value < (1 << (bits - 1)))
        value += ((-1) << bits) + 1;
    return value;
}

NJ_INLINE void njDecodeBlock(nj_component_t* c, unsigned char* out) {
    unsigned char code = 0;
    int value, coef = 0;
    njFillMem(nj.block, 0, sizeof(nj.block));
    c->dcpred += njGetVLC(&nj.vlctab[c->dctabsel][0], NULL);
    nj.block[0] = (c->dcpred) * nj.qtab[c->qtsel][0];
    do {
        value = njGetVLC(&nj.vlctab[c->actabsel][0], &code);
        if (!code) break;  // EOB
        if (!(code & 0x0F) && (code != 0xF0)) njThrow(NJ_SYNTAX_ERROR);
        coef += (code >> 4) + 1;
        if (coef > 63) njThrow(NJ_SYNTAX_ERROR);
        nj.block[(int) njZZ[coef]] = value * nj.qtab[c->qtsel][coef];
    } while (coef < 63);
    for (coef = 0;  coef < 64;  coef += 8)
        njRowIDCT(&nj.block[coef]);
    for (coef = 0;  coef < 8;  ++coef)
        njColIDCT(&nj.block[coef], &out[coef], c->stride);
}

NJ_INLINE void njDecodeScan(void) {
    int i, mbx, mby, sbx, sby;
    int rstcount = nj.rstinterval, nextrst = 0;
    nj_component_t* c;
    njDecodeLength();
    njCheckError();
    if (nj.length < (4 + 2 * nj.ncomp)) njThrow(NJ_SYNTAX_ERROR);
    if (nj.pos[0] != nj.ncomp) njThrow(NJ_UNSUPPORTED);
    njSkip(1);
    for (i = 0, c = nj.comp;  i < nj.ncomp;  ++i, ++c) {
        if (nj.pos[0] != c->cid) njThrow(NJ_SYNTAX_ERROR);
        if (nj.pos[1] & 0xEE) njThrow(NJ_SYNTAX_ERROR);
        c->dctabsel = nj.pos[1] >> 4;
        c->actabsel = (nj.pos[1] & 1) | 2;
        njSkip(2);
    }
    if (nj.pos[0] || (nj.pos[1] != 63) || nj.pos[2]) njThrow(NJ_UNSUPPORTED);
    njSkip(nj.length);
    for (mbx = mby = 0;;) {
        for (i = 0, c = nj.comp;  i < nj.ncomp;  ++i, ++c)
            for (sby = 0;  sby < c->ssy;  ++sby)
                for (sbx = 0;  sbx < c->ssx;  ++sbx) {
                    njDecodeBlock(c, &c->pixels[((mby * c->ssy + sby) * c->stride + mbx * c->ssx + sbx) << 3]);
                    njCheckError();
                }
        if (++mbx >= nj.mbwidth) {
            mbx = 0;
            if (++mby >= nj.mbheight) break;
        }
        if (nj.rstinterval && !(--rstcount)) {
            njByteAlign();
            i = njGetBits(16);
            if (((i & 0xFFF8) != 0xFFD0) || ((i & 7) != nextrst)) njThrow(NJ_SYNTAX_ERROR);
            nextrst = (nextrst + 1) & 7;
            rstcount = nj.rstinterval;
            for (i = 0;  i < 3;  ++i)
                nj.comp[i].dcpred = 0;
        }
    }
    nj.error = __NJ_FINISHED;
}

#if NJ_CHROMA_FILTER

#define CF4A (-9)
#define CF4B (111)
#define CF4C (29)
#define CF4D (-3)
#define CF3A (28)
#define CF3B (109)
#define CF3C (-9)
#define CF3X (104)
#define CF3Y (27)
#define CF3Z (-3)
#define CF2A (139)
#define CF2B (-11)
#define CF(x) njClip(((x) + 64) >> 7)

NJ_INLINE void njUpsampleH(nj_component_t* c) {
    const int xmax = c->width - 3;
    unsigned char *out, *lin, *lout;
    int x, y;
    out = (unsigned char*) njAllocMem((c->width * c->height) << 1);
    if (!out) njThrow(NJ_OUT_OF_MEM);
    lin = c->pixels;
    lout = out;
    for (y = c->height;  y;  --y) {
        lout[0] = CF(CF2A * lin[0] + CF2B * lin[1]);
        lout[1] = CF(CF3X * lin[0] + CF3Y * lin[1] + CF3Z * lin[2]);
        lout[2] = CF(CF3A * lin[0] + CF3B * lin[1] + CF3C * lin[2]);
        for (x = 0;  x < xmax;  ++x) {
            lout[(x << 1) + 3] = CF(CF4A * lin[x] + CF4B * lin[x + 1] + CF4C * lin[x + 2] + CF4D * lin[x + 3]);
            lout[(x << 1) + 4] = CF(CF4D * lin[x] + CF4C * lin[x + 1] + CF4B * lin[x + 2] + CF4A * lin[x + 3]);
        }
        lin += c->stride;
        lout += c->width << 1;
        lout[-3] = CF(CF3A * lin[-1] + CF3B * lin[-2] + CF3C * lin[-3]);
        lout[-2] = CF(CF3X * lin[-1] + CF3Y * lin[-2] + CF3Z * lin[-3]);
        lout[-1] = CF(CF2A * lin[-1] + CF2B * lin[-2]);
    }
    c->width <<= 1;
    c->stride = c->width;
    njFreeMem((void*)c->pixels);
    c->pixels = out;
}

NJ_INLINE void njUpsampleV(nj_component_t* c) {
    const int w = c->width, s1 = c->stride, s2 = s1 + s1;
    unsigned char *out, *cin, *cout;
    int x, y;
    out = (unsigned char*) njAllocMem((c->width * c->height) << 1);
    if (!out) njThrow(NJ_OUT_OF_MEM);
    for (x = 0;  x < w;  ++x) {
        cin = &c->pixels[x];
        cout = &out[x];
        *cout = CF(CF2A * cin[0] + CF2B * cin[s1]);  cout += w;
        *cout = CF(CF3X * cin[0] + CF3Y * cin[s1] + CF3Z * cin[s2]);  cout += w;
        *cout = CF(CF3A * cin[0] + CF3B * cin[s1] + CF3C * cin[s2]);  cout += w;
        cin += s1;
        for (y = c->height - 3;  y;  --y) {
            *cout = CF(CF4A * cin[-s1] + CF4B * cin[0] + CF4C * cin[s1] + CF4D * cin[s2]);  cout += w;
            *cout = CF(CF4D * cin[-s1] + CF4C * cin[0] + CF4B * cin[s1] + CF4A * cin[s2]);  cout += w;
            cin += s1;
        }
        cin += s1;
        *cout = CF(CF3A * cin[0] + CF3B * cin[-s1] + CF3C * cin[-s2]);  cout += w;
        *cout = CF(CF3X * cin[0] + CF3Y * cin[-s1] + CF3Z * cin[-s2]);  cout += w;
        *cout = CF(CF2A * cin[0] + CF2B * cin[-s1]);
    }
    c->height <<= 1;
    c->stride = c->width;
    njFreeMem((void*) c->pixels);
    c->pixels = out;
}

#else

NJ_INLINE void njUpsample(nj_component_t* c) {
    int x, y, xshift = 0, yshift = 0;
    unsigned char *out, *lin, *lout;
    while (c->width < nj.width) { c->width <<= 1; ++xshift; }
    while (c->height < nj.height) { c->height <<= 1; ++yshift; }
    out = (unsigned char*) njAllocMem(c->width * c->height);
    if (!out) njThrow(NJ_OUT_OF_MEM);
    lin = c->pixels;
    lout = out;
    for (y = 0;  y < c->height;  ++y) {
        lin = &c->pixels[(y >> yshift) * c->stride];
        for (x = 0;  x < c->width;  ++x)
            lout[x] = lin[x >> xshift];
        lout += c->width;
    }
    c->stride = c->width;
    njFreeMem((void*) c->pixels);
    c->pixels = out;
}

#endif

NJ_INLINE void njConvert(void) {
    int i;
    nj_component_t* c;
    for (i = 0, c = nj.comp;  i < nj.ncomp;  ++i, ++c) {
        #if NJ_CHROMA_FILTER
            while ((c->width < nj.width) || (c->height < nj.height)) {
                if (c->width < nj.width) njUpsampleH(c);
                njCheckError();
                if (c->height < nj.height) njUpsampleV(c);
                njCheckError();
            }
        #else
            if ((c->width < nj.width) || (c->height < nj.height))
                njUpsample(c);
        #endif
        if ((c->width < nj.width) || (c->height < nj.height)) njThrow(NJ_INTERNAL_ERR);
    }
    if (nj.ncomp == 3) {
        // convert to RGB
        int x, yy;
        unsigned char *prgb = nj.rgb;
        const unsigned char *py  = nj.comp[0].pixels;
        const unsigned char *pcb = nj.comp[1].pixels;
        const unsigned char *pcr = nj.comp[2].pixels;
        for (yy = nj.height;  yy;  --yy) {
            for (x = 0;  x < nj.width;  ++x) {
                register int y = py[x] << 8;
                register int cb = pcb[x] - 128;
                register int cr = pcr[x] - 128;
                *prgb++ = njClip((y            + 359 * cr + 128) >> 8);
                *prgb++ = njClip((y -  88 * cb - 183 * cr + 128) >> 8);
                *prgb++ = njClip((y + 454 * cb            + 128) >> 8);
            }
            py += nj.comp[0].stride;
            pcb += nj.comp[1].stride;
            pcr += nj.comp[2].stride;
        }
    } else if (nj.comp[0].width != nj.comp[0].stride) {
        // grayscale -> only remove stride
        unsigned char *pin = &nj.comp[0].pixels[nj.comp[0].stride];
        unsigned char *pout = &nj.comp[0].pixels[nj.comp[0].width];
        int y;
        for (y = nj.comp[0].height - 1;  y;  --y) {
            njCopyMem(pout, pin, nj.comp[0].width);
            pin += nj.comp[0].stride;
            pout += nj.comp[0].width;
        }
        nj.comp[0].stride = nj.comp[0].width;
    }
}

void njInit(void) {
    njFillMem(&nj, 0, sizeof(nj_context_t));
}

void njDone(void) {
    int i;
    for (i = 0;  i < 3;  ++i)
        if (nj.comp[i].pixels) njFreeMem((void*) nj.comp[i].pixels);
    if (nj.rgb) njFreeMem((void*) nj.rgb);
    njInit();
}

nj_result_t njDecode(const void* jpeg, const int size) {
    njDone();
    nj.pos = (const unsigned char*) jpeg;
    nj.size = size & 0x7FFFFFFF;
    if (nj.size < 2) return NJ_NO_JPEG;
    if ((nj.pos[0] ^ 0xFF) | (nj.pos[1] ^ 0xD8)) return NJ_NO_JPEG;
    njSkip(2);
    while (!nj.error) {
        if ((nj.size < 2) || (nj.pos[0] != 0xFF)) return NJ_SYNTAX_ERROR;
        njSkip(2);
        switch (nj.pos[-1]) {
            case 0xC0: njDecodeSOF();  break;
            case 0xC4: njDecodeDHT();  break;
            case 0xDB: njDecodeDQT();  break;
            case 0xDD: njDecodeDRI();  break;
            case 0xDA: njDecodeScan(); break;
            case 0xFE: njSkipMarker(); break;
            default:
                if ((nj.pos[-1] & 0xF0) == 0xE0)
                    njSkipMarker();
                else
                    return NJ_UNSUPPORTED;
        }
    }
    if (nj.error != __NJ_FINISHED) return nj.error;
    nj.error = NJ_OK;
    njConvert();
    return nj.error;
}

int njGetWidth(void)            { return nj.width; }
int njGetHeight(void)           { return nj.height; }
int njIsColor(void)             { return (nj.ncomp != 1); }
unsigned char* njGetImage(void) { return (nj.ncomp == 1) ? nj.comp[0].pixels : nj.rgb; }
int njGetImageSize(void)        { return nj.width * nj.height * nj.ncomp; }

#endif // _NJ_INCLUDE_HEADER_ONLY

• jo_writeBits     • jo_DCT     • jo_calcBits     • jo_processDU     • jo_write_jpg

/* Public Domain, Simple, Minimalistic JPEG writer - http://jonolick.com
 *
 * Quick Notes:
 * 	Based on a javascript jpeg writer
 * 	JPEG baseline (no JPEG progressive)
 * 	Supports 1, 3 or 4 component input. (luminance, RGB or RGBX)
 *
 * Latest revisions:
 *  1.60 (2019-27-11) Added support for subsampling U,V so that it encodes smaller files. Enabled when quality <= 90.
 *	1.52 (2012-22-11) Added support for specifying Luminance, RGB, or RGBA via comp(onents) argument (1, 3 and 4 respectively). 
 *	1.51 (2012-19-11) Fixed some warnings
 *	1.50 (2012-18-11) MT safe. Simplified. Optimized. Reduced memory requirements. Zero allocations. No namespace polution. Approx 340 lines code.
 *	1.10 (2012-16-11) compile fixes, added docs,
 *		changed from .h to .cpp (simpler to bootstrap), etc
 * 	1.00 (2012-02-02) initial release
 *
 * Basic usage:
 *	char *foo = new char[128*128*4]; // 4 component. RGBX format, where X is unused 
 *	jo_write_jpg("foo.jpg", foo, 128, 128, 4, 90); // comp can be 1, 3, or 4. Lum, RGB, or RGBX respectively.
 * 	
 * */

#ifndef JO_INCLUDE_JPEG_H
#define JO_INCLUDE_JPEG_H

// To get a header file for this, either cut and paste the header,
// or create jo_jpeg.h, #define JO_JPEG_HEADER_FILE_ONLY, and
// then include jo_jpeg.c from it.

// Returns false on failure
extern bool jo_write_jpg(const char *filename, const void *data, int width, int height, int comp, int quality);

#endif // JO_INCLUDE_JPEG_H

#ifndef JO_JPEG_HEADER_FILE_ONLY

#if defined(_MSC_VER) && _MSC_VER >= 0x1400
#define _CRT_SECURE_NO_WARNINGS // suppress warnings about fopen()
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

static const unsigned char s_jo_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 };

static void jo_writeBits(FILE *fp, int &bitBuf, int &bitCnt, const unsigned short *bs) {
	bitCnt += bs[1];
	bitBuf |= bs[0] << (24 - bitCnt);
	while(bitCnt >= 8) {
		unsigned char c = (bitBuf >> 16) & 255;
		putc(c, fp);
		if(c == 255) {
			putc(0, fp);
		}
		bitBuf <<= 8;
		bitCnt -= 8;
	}
}

static void jo_DCT(float &d0, float &d1, float &d2, float &d3, float &d4, float &d5, float &d6, float &d7) {
	float tmp0 = d0 + d7;
	float tmp7 = d0 - d7;
	float tmp1 = d1 + d6;
	float tmp6 = d1 - d6;
	float tmp2 = d2 + d5;
	float tmp5 = d2 - d5;
	float tmp3 = d3 + d4;
	float tmp4 = d3 - d4;

	// Even part
	float tmp10 = tmp0 + tmp3;	// phase 2
	float tmp13 = tmp0 - tmp3;
	float tmp11 = tmp1 + tmp2;
	float tmp12 = tmp1 - tmp2;

	d0 = tmp10 + tmp11; 		// phase 3
	d4 = tmp10 - tmp11;

	float z1 = (tmp12 + tmp13) * 0.707106781f; // c4
	d2 = tmp13 + z1; 		// phase 5
	d6 = tmp13 - z1;

	// Odd part
	tmp10 = tmp4 + tmp5; 		// phase 2
	tmp11 = tmp5 + tmp6;
	tmp12 = tmp6 + tmp7;

	// The rotator is modified from fig 4-8 to avoid extra negations.
	float z5 = (tmp10 - tmp12) * 0.382683433f; // c6
	float z2 = tmp10 * 0.541196100f + z5; // c2-c6
	float z4 = tmp12 * 1.306562965f + z5; // c2+c6
	float z3 = tmp11 * 0.707106781f; // c4

	float z11 = tmp7 + z3;		// phase 5
	float z13 = tmp7 - z3;

	d5 = z13 + z2;			// phase 6
	d3 = z13 - z2;
	d1 = z11 + z4;
	d7 = z11 - z4;
} 

static void jo_calcBits(int val, unsigned short bits[2]) {
	int tmp1 = val < 0 ? -val : val;
	val = val < 0 ? val-1 : val;
	bits[1] = 1;
	while(tmp1 >>= 1) {
		++bits[1];
	}
	bits[0] = val & ((1<<bits[1])-1);
}

static int jo_processDU(FILE *fp, int &bitBuf, int &bitCnt, float *CDU, int du_stride, float *fdtbl, int DC, const unsigned short HTDC[256][2], const unsigned short HTAC[256][2]) {
	const unsigned short EOB[2] = { HTAC[0x00][0], HTAC[0x00][1] };
	const unsigned short M16zeroes[2] = { HTAC[0xF0][0], HTAC[0xF0][1] };

	// DCT rows
	for(int i=0; i<du_stride*8; i+=du_stride) {
		jo_DCT(CDU[i], CDU[i+1], CDU[i+2], CDU[i+3], CDU[i+4], CDU[i+5], CDU[i+6], CDU[i+7]);
	}
	// DCT columns
	for(int i=0; i<8; ++i) {
		jo_DCT(CDU[i], CDU[i+du_stride], CDU[i+du_stride*2], CDU[i+du_stride*3], CDU[i+du_stride*4], CDU[i+du_stride*5], CDU[i+du_stride*6], CDU[i+du_stride*7]);
	}
	// Quantize/descale/zigzag the coefficients
	int DU[64];
	for(int y = 0, j=0; y < 8; ++y) {
		for(int x = 0; x < 8; ++x,++j) {
			int i = y*du_stride+x;
			float v = CDU[i]*fdtbl[j];
			DU[s_jo_ZigZag[j]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f));
		}
	}

	// Encode DC
	int diff = DU[0] - DC; 
	if (diff == 0) {
		jo_writeBits(fp, bitBuf, bitCnt, HTDC[0]);
	} else {
		unsigned short bits[2];
		jo_calcBits(diff, bits);
		jo_writeBits(fp, bitBuf, bitCnt, HTDC[bits[1]]);
		jo_writeBits(fp, bitBuf, bitCnt, bits);
	}
	// Encode ACs
	int end0pos = 63;
	for(; (end0pos>0)&&(DU[end0pos]==0); --end0pos) {
	}
	// end0pos = first element in reverse order !=0
	if(end0pos == 0) {
		jo_writeBits(fp, bitBuf, bitCnt, EOB);
		return DU[0];
	}
	for(int i = 1; i <= end0pos; ++i) {
		int startpos = i;
		for (; DU[i]==0 && i<=end0pos; ++i) {
		}
		int nrzeroes = i-startpos;
		if ( nrzeroes >= 16 ) {
			int lng = nrzeroes>>4;
			for (int nrmarker=1; nrmarker <= lng; ++nrmarker)
				jo_writeBits(fp, bitBuf, bitCnt, M16zeroes);
			nrzeroes &= 15;
		}
		unsigned short bits[2];
		jo_calcBits(DU[i], bits);
		jo_writeBits(fp, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]);
		jo_writeBits(fp, bitBuf, bitCnt, bits);
	}
	if(end0pos != 63) {
		jo_writeBits(fp, bitBuf, bitCnt, EOB);
	}
	return DU[0];
}

bool jo_write_jpg(const char *filename, const void *data, int width, int height, int comp, int quality) {
	// Constants that don't pollute global namespace
	static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0};
	static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11};
	static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d};
	static const unsigned char std_ac_luminance_values[] = {
		0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
		0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
		0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
		0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
		0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
		0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
		0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa
	};
	static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0};
	static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11};
	static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77};
	static const unsigned char std_ac_chrominance_values[] = {
		0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
		0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
		0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
		0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
		0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
		0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
		0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa
	};
	// Huffman tables
	static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}};
	static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}};
	static const unsigned short YAC_HT[256][2] = { 
		{10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0},
		{2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0}
	};
	static const unsigned short UVAC_HT[256][2] = { 
		{0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
		{16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0},
		{1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0}
	};
	static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99};
	static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99};
	static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f };

	if(!data || !filename || !width || !height || comp > 4 || comp < 1 || comp == 2) {
		return false;
	}

	FILE *fp = fopen(filename, "wb");
	if(!fp) {
		return false;
	}

	quality = quality ? quality : 90;
	int subsample = quality <= 90 ? 1 : 0;
	quality = quality < 1 ? 1 : quality > 100 ? 100 : quality;
	quality = quality < 50 ? 5000 / quality : 200 - quality * 2;

	unsigned char YTable[64], UVTable[64];
	for(int i = 0; i < 64; ++i) {
		int yti = (YQT[i]*quality+50)/100;
		YTable[s_jo_ZigZag[i]] = yti < 1 ? 1 : yti > 255 ? 255 : yti;
		int uvti  = (UVQT[i]*quality+50)/100;
		UVTable[s_jo_ZigZag[i]] = uvti < 1 ? 1 : uvti > 255 ? 255 : uvti;
	}

	float fdtbl_Y[64], fdtbl_UV[64];
	for(int row = 0, k = 0; row < 8; ++row) {
		for(int col = 0; col < 8; ++col, ++k) {
			fdtbl_Y[k]  = 1 / (YTable [s_jo_ZigZag[k]] * aasf[row] * aasf[col]);
			fdtbl_UV[k] = 1 / (UVTable[s_jo_ZigZag[k]] * aasf[row] * aasf[col]);
		}
	}

	// Write Headers
	static const unsigned char head0[] = { 0xFF,0xD8,  0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,  0xDB,0,0x84,0 };
	fwrite(head0, sizeof(head0), 1, fp);
	fwrite(YTable, sizeof(YTable), 1, fp);
	putc(1, fp);
	fwrite(UVTable, sizeof(UVTable), 1, fp);
	const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),(unsigned char)(height&0xFF),(unsigned char)(width>>8),(unsigned char)(width&0xFF),3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 };
	fwrite(head1, sizeof(head1), 1, fp);
	fwrite(std_dc_luminance_nrcodes+1, sizeof(std_dc_luminance_nrcodes)-1, 1, fp);
	fwrite(std_dc_luminance_values, sizeof(std_dc_luminance_values), 1, fp);
	putc(0x10, fp); // HTYACinfo
	fwrite(std_ac_luminance_nrcodes+1, sizeof(std_ac_luminance_nrcodes)-1, 1, fp);
	fwrite(std_ac_luminance_values, sizeof(std_ac_luminance_values), 1, fp);
	putc(1, fp); // HTUDCinfo
	fwrite(std_dc_chrominance_nrcodes+1, sizeof(std_dc_chrominance_nrcodes)-1, 1, fp);
	fwrite(std_dc_chrominance_values, sizeof(std_dc_chrominance_values), 1, fp);
	putc(0x11, fp); // HTUACinfo
	fwrite(std_ac_chrominance_nrcodes+1, sizeof(std_ac_chrominance_nrcodes)-1, 1, fp);
	fwrite(std_ac_chrominance_values, sizeof(std_ac_chrominance_values), 1, fp);
	static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 };
	fwrite(head2, sizeof(head2), 1, fp);

	// Encode 8x8 macroblocks
	int ofsG = comp > 1 ? 1 : 0, ofsB = comp > 1 ? 2 : 0;
	const unsigned char *dataR = (const unsigned char *)data;
	const unsigned char *dataG = dataR + ofsG;
	const unsigned char *dataB = dataR + ofsB;
	int DCY=0, DCU=0, DCV=0;
	int bitBuf=0, bitCnt=0;
	if(subsample) {
		for(int y = 0; y < height; y += 16) {
			for(int x = 0; x < width; x += 16) {
				float Y[256], U[256], V[256];
				for(int row = y, pos = 0; row < y+16; ++row) {
					for(int col = x; col < x+16; ++col, ++pos) {
						int prow = row >= height ? height-1 : row;
						int pcol = col >= width ? width-1 : col;
						int p = prow*width*comp + pcol*comp;
						float r = dataR[p], g = dataG[p], b = dataB[p];
						Y[pos]=+0.29900f*r+0.58700f*g+0.11400f*b-128;
						U[pos]=-0.16874f*r-0.33126f*g+0.50000f*b;
						V[pos]=+0.50000f*r-0.41869f*g-0.08131f*b;
					}
				}
				DCY = jo_processDU(fp, bitBuf, bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
				DCY = jo_processDU(fp, bitBuf, bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
				DCY = jo_processDU(fp, bitBuf, bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
				DCY = jo_processDU(fp, bitBuf, bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
				// subsample U,V
				float subU[64], subV[64];
				for(int yy = 0, pos = 0; yy < 8; ++yy) {
					for(int xx = 0; xx < 8; ++xx, ++pos) {
						int j = yy*32+xx*2;
						subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f;
						subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f;
					}
				}
				DCU = jo_processDU(fp, bitBuf, bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
				DCV = jo_processDU(fp, bitBuf, bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
			}
		}
	} else {
		for(int y = 0; y < height; y += 8) {
			for(int x = 0; x < width; x += 8) {
				float Y[64], U[64], V[64];
				for(int row = y, pos = 0; row < y+8; ++row) {
					for(int col = x; col < x+8; ++col, ++pos) {
						int prow = row >= height ? height-1 : row;
						int pcol = col >= width ? width-1 : col;
						int p = prow*width*comp + pcol*comp;
						float r = dataR[p], g = dataG[p], b = dataB[p];
						Y[pos]=+0.29900f*r+0.58700f*g+0.11400f*b-128;
						U[pos]=-0.16874f*r-0.33126f*g+0.50000f*b;
						V[pos]=+0.50000f*r-0.41869f*g-0.08131f*b;
					}
				}
				DCY = jo_processDU(fp, bitBuf, bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT);
				DCU = jo_processDU(fp, bitBuf, bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
				DCV = jo_processDU(fp, bitBuf, bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
			}
		}
	}
	
	// Do the bit alignment of the EOI marker
	static const unsigned short fillBits[] = {0x7F, 7};
	jo_writeBits(fp, bitBuf, bitCnt, fillBits);
	putc(0xFF, fp);
	putc(0xD9, fp);
	fclose(fp);
	return true;
}

#endif

#include "memory.h"
#define uchar unsigned char
genvector(uchar,ucharvector) ;
struct image 
{ uchar *u ; int w,h,ncol ; 
  image() { u = 0 ; w = h = ncol = 0 ; }
  image(int a,int b,int c,uchar *d) 
  { w = a ; h = b ; ncol = c ; u = d ; }
} ; 
image readjpg(char *) ;
void writejpg(image,char *,int) ;

• max     • min     • cjcup     • cjcuplog     • cjcformat     • cjcprint2     • cjcprint1     • complex     • print     • xy     • print     • xi     • ij     • settable     • unset     • double     • print     • free     • cjcupalloc     • cjcuprealloc     • swap     • freename     • charvector     • xivector     • isortup     • realsort     • realsortdown     • xysort     • xisort     • ijsort     • xisortdown     • fupopenread     • fupopenwrite     • freadline     • readline

#ifndef MEMORY_H
#define MEMORY_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

static double max(double a,double b) { if(a>b) return a ; else return b ; }
static double min(double a,double b) { if(a<b) return a ; else return b ; }
static double max(double a,int b) { if(a>b) return a ; else return b ; }
static double min(double a,int b) { if(a<b) return a ; else return b ; }
static double max(int a,double b) { if(a>b) return a ; else return b ; }
static double min(int a,double b) { if(a<b) return a ; else return b ; }
static int max(int a,int b) { if(a>b) return a ; else return b ; }
static int min(int a,int b) { if(a<b) return a ; else return b ; }
#define abnormal(x) (!!((x)-(x))) // x-x forced to boolean

/* ---------------------------- define throwing up -------------------------- */

static int cjcupline=-1,cjcperror=0 ;
static const char *cjcupfile="",*cjcupfunc="" ;

static int cjcup(const char *m,...) 
{ va_list vl ; 
  fprintf(stderr,"*** Error at line %d of %s [function %s]:\n",
                 cjcupline,cjcupfile,cjcupfunc) ;
  if(cjcperror) perror(0) ; 
  va_start(vl,m) ; 
  vfprintf(stderr,m,vl) ; 
  va_end(vl) ; 
  fprintf(stderr,"\n") ; 
  fflush(0) ; 
  return 0 ; 
} ;
static void cjcuplog(const char *f,int l,const char *ff) 
{ cjcupfile = f ; cjcupline = l ; cjcupfunc = ff ; } 

#define up (cjcuplog(__FILE__,__LINE__,__PRETTY_FUNCTION__),cjcup)

/* -------------------------- simple ordered pairs -------------------------- */

static char cjcbuf[600]={0} ;
static int cjcind = 40 , cjcint = 0 ; 
static void cjcformat(char *fmt)
{ int i ; 
  if(strlen(fmt)>18) throw up("overlong cjcprint format %s") ;
  strcpy(cjcbuf,fmt) ; 
  for(i=0;fmt[i]&&fmt[i]!='%';i++) ;
  for(;fmt[i]&&(fmt[i]=='l'||(fmt[i]<'a'||fmt[i]>'z'));i++) ;
  cjcint = (fmt[i]=='d') ;
}
static char *cjcprint2(double x,double y)
{ if(cjcbuf[0]==0) cjcformat((char *)"%.1f") ; 
  char *ptr = cjcbuf + cjcind ; 
  cjcbuf[cjcind] = '(' ;
  if(cjcint) cjcind += 2 + snprintf(cjcbuf+cjcind+1,100,cjcbuf,(int)x) ; 
  else cjcind += 2 + snprintf(cjcbuf+cjcind+1,100,cjcbuf,x) ; 
  cjcbuf[cjcind-1] = ',' ;
  if(cjcint) cjcind += 2 + snprintf(cjcbuf+cjcind,100,cjcbuf,(int)y) ; 
  else cjcind += 2 + snprintf(cjcbuf+cjcind,100,cjcbuf,y) ; 
  cjcbuf[cjcind-2] = ')' ;
  cjcbuf[cjcind-1] = 0 ;
  if(cjcind>600) throw up("data overflow on printing an ordered pair") ; 
  if(cjcind>500) cjcind = 40 ;
  return ptr ; 
}
static char *cjcprint2(char *fmt,double x,double y)
{ cjcformat(fmt) ; return cjcprint2(x,y) ; }

static char *cjcprint1(double x)
{ if(cjcbuf[0]==0) cjcformat((char *)"%.1f") ; 
  char *ptr = cjcbuf + cjcind ; 
  if(cjcint) cjcind += 1 + snprintf(cjcbuf+cjcind,100,cjcbuf,(int)x) ; 
  else cjcind += 1 + snprintf(cjcbuf+cjcind,100,cjcbuf,x) ; 
  if(cjcind>600) throw up("data overflow on printing") ; 
  if(cjcind>500) cjcind = 40 ;
  return ptr ; 
}
static char *cjcprint1(char *fmt,double x)
{ cjcformat(fmt) ; return cjcprint1(x) ; }

struct complex
{ double re,im ; 
  complex() { re = im = 0 ; } 
  complex(double x) { re = x ; im = 0 ; } 
  complex(double x,double y) { re = x ; im = y ; } 
  complex &operator=(double x) { re = x ; im = 0 ; return *this ; }
  char *print(char *fmt) { return cjcprint2(fmt,re,im) ; }
  char *print() { return cjcprint2(re,im) ; }
} ;
struct xy 
{ double x,y ; 
  xy() { x = y = 0 ; } 
  xy(double xx) { x = xx ; y = 0 ; } 
  xy(double xx,double yy) { x = xx ; y = yy ; } 
  xy(double xx,double(*f)(double)) { x = xx ; y = f(x) ; } 
  xy &operator=(double xx) { x = xx ; y = 0 ; return *this ; }
  char *print(char *fmt) { return cjcprint2(fmt,x,y) ; }
  char *print() { return cjcprint2(x,y) ; }
} ;
inline bool operator==(xy a,xy b) { return a.x==b.x&&a.y==b.y ; } 
struct xi 
{ double x ; int i ; 
  xi() { x = i = 0 ; } 
  xi(double a,int b) { x = a ; i = b ; } 
} ;
struct ij 
{ int i,j ; 
  ij() { j = i = 0 ; } 
  ij(int a,int b) { i = a ; j = b ; } 
} ;
/* --------------------------------- settables ------------------------------ */

struct settable
{ private: double x ;
  public:
    bool set ; 
    settable() { set = 0 ; }
    settable(double y) 
    { if(abnormal(y)) throw up("setting a settable to %.1e",y) ; 
      set = 1 ; 
      x = y ; 
    }
    settable unset() { return this[0] = settable() ; }
    operator double() 
    { if(!set) throw up("accessing an unset settable") ; return x ; }
    settable &operator=(double y) 
    { if(abnormal(y)) throw up("assigning %.1e to a settable",y) ;
      set = 1 ; 
      x = y ; 
      return *this ; 
    }
    char *print() 
    { if(set) return cjcprint1(x) ;
      char *ptr=cjcbuf+cjcind ;
      strcpy(ptr,"undef") ; 
      cjcind += 6 ; 
      if(cjcind>500) cjcind = 40 ; 
      return ptr ; 
    }
    char *print(char *fmt) { cjcformat(fmt) ; return print() ; }
} ;
inline bool operator==(settable x,settable y) 
{ return (x.set==0&&y.set==0)||((double)x)==(double(y)) ; } 
inline bool operator==(settable x,double y) 
{ return x.set && y==(double)x ; } 
inline bool operator==(double x,settable y) { return (y==x) ; }
inline bool operator!=(settable x,settable y) { return !(x==y) ; } 

inline settable operator+=(settable &x,double y) 
{ if(!x.set) throw up("incrementing an unset settable") ; 
  return x = settable(x+y) ; 
}
inline settable operator-=(settable &x,double y) 
{ if(!x.set) throw up("decrementing an unset settable") ; 
  return x = settable(x-y) ; 
}
inline settable operator*=(settable &x,double y) 
{ if(y==0) return x = settable(0) ; 
  if(!x.set) throw up("*= applied to an unset settable") ; 
  return x = settable(x*y) ; 
}
inline settable operator/=(settable &x,double y) 
{ if(y==0) throw up("settable divided by 0") ; 
  if(!x.set) throw up("/= applied to an unset settable") ; 
  return x = settable(x/y) ; 
}

inline settable max(settable a,settable b) 
{ if(a.set&&(!b.set||a>b)) return a ; else return b ; }
inline settable min(settable a,settable b) 
{ if(a.set&&(!b.set||a<b)) return a ; else return b ; }

inline settable max(settable a,double b) 
{ if(a.set&&a>b) return a ; else return b ; }
inline settable min(settable a,double b) 
{ if(a.set&&a<b) return a ; else return b ; }
inline settable max(double a,settable b) 
{ if(b.set&&b>a) return b ; else return a ; }
inline settable min(double a,settable b) 
{ if(b.set&&b<a) return b ; else return a ; }

inline settable max(settable a,int b) 
{ if(a.set&&a>b) return a ; else return b ; }
inline settable min(settable a,int b) 
{ if(a.set&&a<b) return a ; else return b ; }
inline settable max(int a,settable b) 
{ if(b.set&&b>a) return b ; else return a ; }
inline settable min(int a,settable b) 
{ if(b.set&&b<a) return b ; else return a ; }

/* ------------------------------ variadic free() --------------------------- */

static void free(void *a,void *b) { free(a) ; free(b) ; } 
static void free(void *a,void *b,void *c) 
{ free(a) ; free(b) ; free(c) ; } 
static void free(void *a,void *b,void *c,void *d) 
{ free(a) ; free(b) ; free(c) ; free(d) ; } 
static void free(void *a,void *b,void *c,void *d,void *e) 
{ free(a) ; free(b) ; free(c) ; free(d) ; free(e) ; } 
static void free(void *a,void *b,void *c,void *d,void *e,void *f) 
{ free(a) ; free(b) ; free(c) ; free(d) ; free(e) ; free(f) ; } 
static void free(void *a,void *b,void *c,void *d,void *e,void *f,void *g) 
{ free(a) ; free(b) ; free(c) ; free(d) ; free(e) ; free(f) ; free(g) ; } 
static void free(void *a,void *b,void *c,void *d,
                 void *e,void *f,void *g,void *h) 
{ free(a) ; free(b) ; free(c) ; free(d) ; 
  free(e) ; free(f) ; free(g) ; free(h) ; 
} 
/* ------------------------- robust allocs ---------------------------- */

static void *cjcupalloc(int a,int b)
{ if(a<0||b<0) throw cjcup("negative length %d requested from cjcalloc.",a*b) ; 
  void *p=calloc(a,b) ; 
  if(p==0) throw cjcup("cjcalloc unable to allocate %d bytes of memory.",b) ; 
  memset(p,0,a*b) ; 
  return p ; 
} 
static void *cjcuprealloc(void *a,int b)
{ if(b<0) throw cjcup("negative length %d requested from cjcrealloc.",b) ; 
  if(a==0&&b==0) return 0 ; 
  void *p=realloc(a,b) ; 
  if(b>0&&p==0) 
    throw cjcup("cjcrealloc unable to reallocate %x to %d bytes.",a,b) ;
  return p ; 
} 
#define cjcalloc (cjcuplog(__FILE__,__LINE__,__PRETTY_FUNCTION__),cjcupalloc)
#define cjcrealloc (cjcuplog(__FILE__,__LINE__,__PRETTY_FUNCTION__),cjcuprealloc)

/* ---------------------------- generic matrix ------------------------------ */

#define genvector(type,vecname)                         \
static type *vecname(int n)                             \
{ return (type*) cjcalloc(n,sizeof(type)) ; }           \
static type *vecname(type *x,int n)                     \
{ return (type *) cjcrealloc(x,n*sizeof(type)) ; }      \
static void swap(type &a,type &b) { type c=b ; b = a ; a = c ; }

#define genmatrix(type,vecname,name,freename)           \
static type *vecname(int n)                             \
{ return (type*) cjcalloc(n,sizeof(type)) ; }           \
static type *vecname(type *x,int n)                     \
{ return (type *) cjcrealloc(x,n*sizeof(type)) ; }      \
static type **name(int m,int n)                         \
{ type **a = (type **) cjcalloc(m,sizeof(type *)) ;     \
  a[0] = vecname(m*n) ;                                 \
  for(int i=1;i<m;i++) a[i] = a[i-1] + n ;              \
  return a ;                                            \
}                                                       \
static type ***name(int m,int n,int l)                  \
{ int i ;                                               \
  type ***a = (type ***) cjcalloc(m,sizeof(type **)) ;  \
  a[0] = (type **) cjcalloc(m*n,sizeof(type *)) ;       \
  for(i=1;i<m;i++) a[i] = a[i-1] + n ;                  \
  a[0][0] = vecname(m*n*l) ;                            \
  for(i=1;i<m*n;i++) a[0][i] = a[0][i-1] + l ;          \
  return a ;                                            \
}                                                       \
static void freename(type **a) { if(a) free(a[0],a) ; } \
static void freename(type **a,type **b)                 \
{ freename(a) ; freename(b) ; }                         \
static void freename(type **a,type **b,type **c)        \
{ freename(a) ; freename(b) ; freename(c) ; }           \
static void freename(type **a,type **b,type **c,type **d)   \
{ freename(a) ; freename(b) ; freename(c) ; freename(d) ; } \
static void freename(type **a,type **b,type **c,type **d,   \
                     type **e)                              \
{ freename(a) ; freename(b) ; freename(c) ; freename(d) ;   \
  freename(e) ; }                                           \
static void freename(type **a,type **b,type **c,type **d,   \
                     type **e,type **f)                     \
{ freename(a) ; freename(b) ; freename(c) ; freename(d) ;   \
  freename(e) ; freename(f) ; }                             \
static void freename(type ***a) { if(a) free(a[0][0],a[0],a) ; } \
static void swap(type &a,type &b) { type c=b ; b = a ; a = c ; } 

/* --------------------------- matrix instances ----------------------------- */

genmatrix(double,vector,matrix,freematrix) ; 
genmatrix(int,ivector,imatrix,freeimatrix) ; 
genmatrix(xi,xivector,ximatrix,freeximatrix) ; 
genmatrix(ij,ijvector,ijmatrix,freeijmatrix) ; 
genmatrix(xy,xyvector,xymatrix,freexymatrix) ; 
genmatrix(char,charvector,charmatrix,freecharmatrix) ; 
genmatrix(char*,strvector,strmatrix,freestrmatrix) ; 
genmatrix(short,shortvector,shortmatrix,freeshortmatrix) ; 
genmatrix(complex,cvector,cmatrix,freecmatrix) ; 
genmatrix(settable,setvector,setmatrix,freesetmatrix) ; 

// a couple of specials
static char *charvector(char *c) 
{ char *ret=charvector(1+strlen(c)) ; strcpy(ret,c) ; return ret ; } 
static xi *xivector(double *x,int n)
{ xi *y=xivector(n) ; 
  for(int i=0;i<n;i++) y[i] = xi(x[i],i) ; 
  return y ; 
}
/* ----------------------------- generic sorts ------------------------------ */

#define shellsortup(x,n,type,field)                                    \
{ int i,j,inc ; type y ;                                               \
  for(inc=1;1+3*inc<(n);inc=1+3*inc) ;                                 \
  for(;inc>0;inc/=3) for(i=inc;i<(n);i++)                              \
  { for(y=x[i],j=i;j>=inc;j-=inc)                                      \
    { if(y.field<x[j-inc].field) x[j] = x[j-inc] ; else break ; }      \
    x[j] = y ;                                                         \
  }                                                                    \
}
#define shellsortdown(x,n,type,field)                                  \
{ int i,j,inc ; type y ;                                               \
  for(inc=1;1+3*inc<(n);inc=1+3*inc) ;                                 \
  for(;inc>0;inc/=3) for(i=inc;i<(n);i++)                              \
  { for(y=x[i],j=i;j>=inc;j-=inc)                                      \
    { if(y.field>x[j-inc].field) x[j] = x[j-inc] ; else break ; }      \
    x[j] = y ;                                                         \
  }                                                                    \
}
#define shsortup(x,n,type)                                             \
{ int i,j,inc ; type y ;                                               \
  for(inc=1;1+3*inc<(n);inc=1+3*inc) ;                                 \
  for(;inc>0;inc/=3) for(i=inc;i<(n);i++)                              \
  { for(y=x[i],j=i;j>=inc;j-=inc)                                      \
    { if(y<x[j-inc]) x[j] = x[j-inc] ; else break ; }                  \
    x[j] = y ;                                                         \
  }                                                                    \
}
#define shsortdown(x,n,type)                                           \
{ int i,j,inc ; type y ;                                               \
  for(inc=1;1+3*inc<(n);inc=1+3*inc) ;                                 \
  for(;inc>0;inc/=3) for(i=inc;i<(n);i++)                              \
  { for(y=x[i],j=i;j>=inc;j-=inc)                                      \
    { if(y>x[j-inc]) x[j] = x[j-inc] ; else break ; }                  \
    x[j] = y ;                                                         \
  }                                                                    \
}
/* ----------------------------- sundry sorts ------------------------------- */

static void isortup(int *u,int n) { shsortup(u,n,int) ; }
static void realsort(double *u,int n) { shsortup(u,n,double) ; }
static void realsortdown(double *u,int n) { shsortdown(u,n,double) ; }
static void xysort(xy *u,int n) { shellsortup(u,n,xy,x) ; }
static void xisort(xi *u,int n) { shellsortup(u,n,xi,x) ; }
static void ijsort(ij *u,int n) { shellsortup(u,n,ij,i) ; }
static void xisortdown(xi *u,int n) { shellsortdown(u,n,xi,x) ; } 

/* -------------------------- define robust fopens -------------------------- */

static FILE *fupopenread(char *name)
{ FILE *f ; 
  if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdin ; 
  else f = fopen(name,"r") ; 
  if(f==0) 
  { cjcperror = 1 ; 
    throw cjcup("Your input file %s could not be found.",name) ; 
  }
  return f ; 
} 
static FILE *fupopenwrite(char *name)
{ FILE *f ; 
  if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdout ; 
  else f = fopen(name,"w") ; 
  if(f==0) 
  { cjcperror = 1 ; 
    throw cjcup("Unable to write to your file %s.",name) ; 
  }
  return f ; 
} 
#define fopenread (cjcuplog(__FILE__,__LINE__,__PRETTY_FUNCTION__),fupopenread)
#define fopenwrite (cjcuplog(__FILE__,__LINE__,__PRETTY_FUNCTION__),fupopenwrite)
static char *freadline(FILE *ifl)
{ char *s=0 ; 
  int slen,ns,c,i ; 
  for(slen=ns=0;;)
  { c = fgetc(ifl) ; 
    if(c==EOF||c=='\n') 
    { if(slen>ns+1||(ns==0&&c=='\n')) s = charvector(s,ns+1) ; return s ; }
    if(ns>=slen-1)
    { slen += 20 + slen/2 ; 
      s = charvector(s,slen) ; 
      for(i=ns;i<slen;i++) s[i] = 0 ; 
    }
    s[ns++] = (char) c ; 
  }
}
static char *readline() { return freadline(stdin) ; } 
#endif

These are the first bash scripts I have ever written. What a notation! Don’t ask me what any of it means – I haven’t a clue.

#!/bin/bash
n=0
for thumb in *@t.jpg ; do
    len=${#thumb}
    icon=${thumb:0:len-6}"@i.jpg"
    if [ ! -f "$icon" ]; then
        pixrescale $thumb $icon 60 52 52
        ((n++))
    fi
done
if((n>>0)); then
    echo $n " icons created"
fi

#!/bin/bash
for thumb in *@t.jpg ; do
    echo "processing "$thumb
    pixcompact $thumb 
done

#!/bin/bash
big=5
w=196
h=140
narg=$#
if (( narg > 0 )) ; then 
    big=$1 
fi 
small=$((big - 1))

if (( narg > 2 )) ; then 
    w=$2 
    h=$3
fi

for image in *@${big}.jpg ; do
    if [ -f $image ]; then
        len=${#image}
        smallimage=${image:0:len-5}$small".jpg"
        if [ ! -f $smallimage ]; then
            pixdownscale $image $w $h
        fi
    fi
done

pixrescale.c pixcompact.c pixdownscale.c pixrewrite.c
rescale.c tinyreadjpg.c readjpg.c 
nanojpeg.c jpg.c 
readjpg.h memory.h 
pixicon.sh pixcompact.sh pixdownscale.sh