cropng tool added.

This commit is contained in:
Marisa the Magician 2021-03-05 11:03:53 +01:00
commit 1aba0a104a
2 changed files with 306 additions and 1 deletions

304
cropng.c Normal file
View file

@ -0,0 +1,304 @@
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <endian.h>
#include <png.h>
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 '%lu' ('8' expected).\n",unk[i].size);
png_destroy_read_struct(&pngp,&infp,0);
fclose(pf);
return 0;
}
*x = be32toh(*(uint32_t*)unk[i].data);
*y = be32toh(*(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] = htobe32(x);
grabs[1] = htobe32(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;
}
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;
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 )
{
if ( argc < 2 )
{
fprintf(stderr,
"usage: cropng <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"
"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=1; i<argc; i++ ) processpic(argv[i]);
return 0;
}