stuff/cropng.c

348 lines
9 KiB
C

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <png.h>
uint32_t endianswap( uint32_t n )
{
// if we're in a big endian system, we don't need this
uint16_t testme = 0x1234;
if ( *(uint8_t*)(&testme) == 0x12 ) return n;
uint32_t on;
for ( int i=0; i<4; i++ )
*(((uint8_t*)(&on))+i) = *(((uint8_t*)(&n))+(3-i));
return on;
}
const char* fmtname( int bd, int col )
{
if ( col == PNG_COLOR_TYPE_PALETTE )
return " paletted";
else if ( col == PNG_COLOR_TYPE_GRAY_ALPHA )
return (bd==16)?" 16bpc grayscale":" grayscale";
else if ( col == PNG_COLOR_TYPE_RGB_ALPHA )
return (bd==16)?" 16bpc rgb":" rgb";
return "";
}
int loadpng( const char *fname, uint8_t **idata, uint32_t *w, uint32_t *h, int32_t *x, int32_t *y, int *pxsize, png_color **plte, int *nplt, uint8_t **trns, int *ntrns, uint8_t *bd, uint8_t *col )
{
if ( !fname ) return 0;
png_structp pngp;
png_infop infp;
unsigned int sread = 0;
FILE *pf;
if ( !(pf = fopen(fname,"rb")) )
{
fprintf(stderr," Cannot open for reading: %s.\n",strerror(errno));
return 0;
}
pngp = png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0,0);
if ( !pngp )
{
fclose(pf);
return 0;
}
infp = png_create_info_struct(pngp);
if ( !infp )
{
fclose(pf);
png_destroy_read_struct(&pngp,0,0);
return 0;
}
if ( setjmp(png_jmpbuf(pngp)) )
{
png_destroy_read_struct(&pngp,&infp,0);
fclose(pf);
return 0;
}
png_init_io(pngp,pf);
png_set_sig_bytes(pngp,sread);
png_set_keep_unknown_chunks(pngp,PNG_HANDLE_CHUNK_ALWAYS,(uint8_t*)"grAb\0alPh\0",2);
png_read_png(pngp,infp,PNG_TRANSFORM_PACKING,0);
*w = png_get_image_width(pngp,infp);
*h = png_get_image_height(pngp,infp);
*bd = png_get_bit_depth(pngp,infp);
if ( (*bd != 8) && (*bd != 16) )
{
fprintf(stderr," Unsupported bit depth of '%d' (only 8bpc and 16bpc supported).\n",*bd);
png_destroy_read_struct(&pngp,&infp,0);
fclose(pf);
return 0;
}
*col = png_get_color_type(pngp,infp);
if ( (*col == PNG_COLOR_TYPE_GRAY) || (*col == PNG_COLOR_TYPE_RGB) )
{
fprintf(stderr," Image has no alpha channel.\n");
png_destroy_read_struct(&pngp,&infp,0);
fclose(pf);
return 0;
}
else if ( *col == PNG_COLOR_TYPE_PALETTE )
{
*pxsize = 1;
if ( !png_get_valid(pngp,infp,PNG_INFO_tRNS) )
{
fprintf(stderr," Paletted image has no tRNS chunk.\n");
png_destroy_read_struct(&pngp,&infp,0);
fclose(pf);
return 0;
}
// for whatever reason these arrays have to be allocated and memcpy'd
// otherwise they'll be corrupted when we use them later
//
// there's no logical explanation for this
// the manual doesn't mention anything about it
uint8_t *trns_local;
png_get_tRNS(pngp,infp,&trns_local,ntrns,0);
*trns = malloc(*ntrns);
memcpy(*trns,trns_local,*ntrns);
png_color *plte_local;
png_get_PLTE(pngp,infp,&plte_local,nplt);
*plte = malloc(sizeof(png_color)*(*nplt));
memcpy(*plte,plte_local,sizeof(png_color)*(*nplt));
}
else if ( *col == PNG_COLOR_TYPE_GRAY_ALPHA ) *pxsize = (*bd>>3)*2;
else if ( *col == PNG_COLOR_TYPE_RGB_ALPHA ) *pxsize = (*bd>>3)*4;
else
{
fprintf(stderr," Image has unrecognized color type '%d'.\n",*col);
png_destroy_read_struct(&pngp,&infp,0);
fclose(pf);
return 0;
}
png_unknown_chunkp unk;
int nunk = png_get_unknown_chunks(pngp,infp,&unk);
for ( int i=0; i<nunk; i++ )
{
if ( !memcmp(unk[i].name,"alPh",4) )
{
fprintf(stderr," Images with alPh chunks are unsupported.\n");
png_destroy_read_struct(&pngp,&infp,0);
fclose(pf);
return 0;
}
if ( memcmp(unk[i].name,"grAb",4) ) continue;
if ( unk[i].size != 8 )
{
fprintf(stderr," grAb chunk has invalid size '%zu' ('8' expected).\n",unk[i].size);
png_destroy_read_struct(&pngp,&infp,0);
fclose(pf);
return 0;
}
*x = endianswap(*(uint32_t*)unk[i].data);
*y = endianswap(*(uint32_t*)(unk[i].data+4));
}
if ( *x || *y ) printf(" Read %ux%u%s image with offsets %+d,%+d.\n",*w,*h,fmtname(*bd,*col),*x,*y);
else printf(" Read %ux%u%s image.\n",*w,*h,fmtname(*bd,*col));
int rbytes = png_get_rowbytes(pngp,infp);
*idata = calloc(rbytes,*h);
png_bytepp rptr = png_get_rows(pngp,infp);
for ( uint32_t i=0; i<(*h); i++ ) memcpy((*idata)+(rbytes*i),rptr[i],rbytes);
png_destroy_read_struct(&pngp,&infp,0);
fclose(pf);
return 1;
}
int savepng( const char *fname, uint8_t *odata, uint32_t w, uint32_t h, int32_t x, int32_t y, int pxsize, png_color *plte, int nplt, uint8_t *trns, int ntrns, uint8_t bd, uint8_t col )
{
if ( !fname ) return 0;
png_structp pngp;
png_infop infp;
FILE *pf;
if ( !(pf = fopen(fname,"wb")) )
{
fprintf(stderr," Cannot open for writing: %s.\n",strerror(errno));
return 0;
}
pngp = png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0);
if ( !pngp )
{
fclose(pf);
return 0;
}
infp = png_create_info_struct(pngp);
if ( !infp )
{
fclose(pf);
png_destroy_write_struct(&pngp,0);
return 0;
}
if ( setjmp(png_jmpbuf(pngp)) )
{
png_destroy_write_struct(&pngp,&infp);
fclose(pf);
return 0;
}
png_init_io(pngp,pf);
png_set_IHDR(pngp,infp,w,h,bd,col,PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE,PNG_FILTER_TYPE_BASE);
if ( col == PNG_COLOR_TYPE_PALETTE )
{
if ( trns ) png_set_tRNS(pngp,infp,trns,ntrns,0);
png_set_PLTE(pngp,infp,plte,nplt);
}
if ( x || y )
{
uint32_t grabs[2];
grabs[0] = endianswap(x);
grabs[1] = endianswap(y);
png_unknown_chunk grab =
{
.name = "grAb",
.data = (uint8_t*)grabs,
.size = 8,
.location = PNG_HAVE_IHDR
};
png_set_unknown_chunks(pngp,infp,&grab,1);
}
png_set_compression_level(pngp,9);
png_write_info(pngp,infp);
for ( uint32_t i=0; i<h; i++ ) png_write_row(pngp,odata+(w*pxsize*i));
png_write_end(pngp,infp);
png_destroy_write_struct(&pngp,&infp);
fclose(pf);
return 1;
}
int nocropx = 0, nocropy = 0;
void processpic( const char *fname )
{
printf("Processing '%s'...\n",fname);
uint8_t *idata = 0;
png_color *plte = 0;
uint8_t *trns = 0;
uint32_t w = 0, h = 0;
int32_t x = 0, y = 0;
int pxsize = 0;
int nplt = 0;
int ntrns = 0;
uint8_t bd = 0, col = 0;
if ( !loadpng(fname,&idata,&w,&h,&x,&y,&pxsize,&plte,&nplt,&trns,&ntrns,&bd,&col) )
return;
// crop spots
uint32_t cropx = w, cropy = h, cropw = 0, croph = 0;
for ( uint32_t cy=0; cy<h; cy++ ) for ( uint32_t cx=0; cx<w; cx++ )
{
switch ( col )
{
case PNG_COLOR_TYPE_PALETTE:
{
uint8_t i = idata[cy*w+cx];
if ( (i < ntrns) && !trns[i] ) continue;
}
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
if ( bd == 16 )
{
uint16_t a = ((uint16_t*)idata)[(cy*w+cx)*2+1];
if ( !a ) continue;
}
else
{
uint8_t a = idata[(cy*w+cx)*2+1];
if ( !a ) continue;
}
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
if ( bd == 16 )
{
uint16_t a = ((uint16_t*)idata)[(cy*w+cx)*4+3];
if ( !a ) continue;
}
else
{
uint8_t a = idata[(cy*w+cx)*4+3];
if ( !a ) continue;
}
break;
}
if ( cx < cropx ) cropx = cx;
if ( cy < cropy ) cropy = cy;
if ( cx > cropw ) cropw = cx;
if ( cy > croph ) croph = cy;
}
printf(" Crop region: (%u,%u) (%u,%u).\n",cropx,cropy,cropw,croph);
cropw -= cropx-1;
croph -= cropy-1;
if ( nocropx )
{
cropx = 0;
cropw = w;
}
if ( nocropy )
{
cropy = 0;
croph = h;
}
if ( (cropx >= w) || (cropy >= h) || (cropw <= 0) || (croph <= 0) )
{
printf(" Image crops to zero size, skipping.\n");
free(idata);
if ( plte ) free(plte);
if ( trns ) free(trns);
return;
}
uint8_t *odata = calloc(cropw*croph,pxsize);
uint32_t ow = cropw, oh = croph;
int32_t ox = x-cropx, oy = y-cropy;
for ( uint32_t cy=0; cy<croph; cy++ )
{
uint32_t ccy = cy+cropy;
uint32_t ccx = cropx;
memcpy(odata+cy*ow*pxsize,idata+(ccy*w+ccx)*pxsize,ow*pxsize);
}
char bname[256];
snprintf(bname,256,"%s.bak",fname);
rename(fname,bname);
if ( ox || oy ) printf(" Save %ux%u%s image with offsets %+d,%+d.\n",ow,oh,fmtname(bd,col),ox,oy);
else printf(" Save %ux%u%s image.\n",ow,oh,fmtname(bd,col));
if ( !savepng(fname,odata,ow,oh,ox,oy,pxsize,plte,nplt,trns,ntrns,bd,col) )
rename(bname,fname);
else remove(bname);
free(idata);
if ( plte ) free(plte);
if ( trns ) free(trns);
free(odata);
}
int main( int argc, char **argv )
{
int ac = 1;
if ( (argc > ac) && !strcmp(argv[ac],"-nx") )
{
nocropx = 1;
ac++;
}
if ( (argc > ac) && !strcmp(argv[ac],"-ny") )
{
nocropy = 1;
ac++;
}
if ( argc <= ac )
{
fprintf(stderr,
"usage: cropng [-nx] [-ny] <file> ...\n"
"\n"
"cropng will trim excess transparency from PNG images while updating grAb\n"
"offsets to compensate (existing ones will be re-used as a base)\n"
"\n"
"if the -nx argument is passed, it won't trim the width.\n"
"if the -ny argument is passed, it won't trim the height.\n"
"(if both arguments are passed, the program will not alter the image data)\n"
"\n"
"files are updated in-place, but a backup will be kept in case errors happen.\n"
"\n"
"cropng supports paletted, color and grayscale PNGs at both 8bpc and 16bpc.\n"
"it cannot handle files with alPh chunks, nor bit depths other than 8 and 16.\n"
"\n"
"note: any extra chunks such as comments or exif data will be stripped.\n"
);
return 1;
}
for ( int i=ac; i<argc; i++ ) processpic(argv[i]);
return 0;
}