#include "puzzle_common.h" #include "puzzle_p.h" #include "puzzle.h" #include "globals.h" static void puzzle_init_view(PuzzleView * const view) { view->width = view->height = 0U; view->sizeof_map = (size_t) 0U; view->map = NULL; } static void puzzle_free_view(PuzzleView * const view) { free(view->map); view->map = NULL; } static void puzzle_init_avglvls(PuzzleAvgLvls * const avglvls) { avglvls->lambdas = 0U; avglvls->sizeof_lvls = (size_t) 0U; avglvls->lvls = NULL; } static void puzzle_free_avglvls(PuzzleAvgLvls * const avglvls) { free(avglvls->lvls); avglvls->lvls = NULL; } void puzzle_init_dvec(PuzzleContext * const context, PuzzleDvec * const dvec) { (void) context; dvec->sizeof_vec = dvec->sizeof_compressed_vec = (size_t) 0U; dvec->vec = NULL; } void puzzle_free_dvec(PuzzleContext * const context, PuzzleDvec * const dvec) { (void) context; free(dvec->vec); dvec->vec = NULL; } #define MAX_SIGNATURE_LENGTH 8U static PuzzleImageTypeCode puzzle_get_image_type_from_header(const unsigned char * const header) { static const PuzzleImageType image_types[] = { { (size_t) 4U, (const unsigned char *) "GIF8", PUZZLE_IMAGE_TYPE_GIF }, { (size_t) 3U, (const unsigned char *) "\xff\xd8\xff", PUZZLE_IMAGE_TYPE_JPEG }, { (size_t) 8U, (const unsigned char *) "\x89PNG\r\n\x1a\n", PUZZLE_IMAGE_TYPE_PNG }, { (size_t) 0U, NULL, PUZZLE_IMAGE_TYPE_UNKNOWN } }; const PuzzleImageType *image_type = image_types; PuzzleImageTypeCode ret = PUZZLE_IMAGE_TYPE_UNKNOWN; do { if (image_type->sizeof_signature > MAX_SIGNATURE_LENGTH) { puzzle_err_bug(__FILE__, __LINE__); } if (memcmp(header, image_type->signature, image_type->sizeof_signature) == 0) { ret = image_type->image_type_code; break; } image_type++; } while (image_type->signature != NULL); return ret; } static PuzzleImageTypeCode puzzle_get_image_type_from_fp(FILE * const fp) { unsigned char header[MAX_SIGNATURE_LENGTH]; PuzzleImageTypeCode ret = PUZZLE_IMAGE_TYPE_ERROR; fpos_t pos; if (fgetpos(fp, &pos) != 0) { return PUZZLE_IMAGE_TYPE_ERROR; } rewind(fp); if (fread(header, (size_t) 1U, sizeof header, fp) != sizeof header) { goto bye; } ret = puzzle_get_image_type_from_header(header); bye: if (fsetpos(fp, &pos) != 0) { puzzle_err_bug(__FILE__, __LINE__); } return ret; } static int puzzle_autocrop_axis(PuzzleContext * const context, PuzzleView * const view, unsigned int * const crop0, unsigned int * const crop1, const unsigned int axisn, const unsigned int axiso, const int omaptrinc, const int nmaptrinc) { double *chunk_contrasts; size_t sizeof_chunk_contrasts; double chunk_contrast = 0.0, total_contrast = 0.0, barrier_contrast; unsigned char level = 0U; unsigned char previous_level = 0U; unsigned int chunk_n, chunk_o; unsigned int chunk_n1, chunk_o1; unsigned int max_crop; const unsigned char *maptr; chunk_n1 = axisn - 1U; chunk_o1 = axiso - 1U; *crop0 = 0U; *crop1 = chunk_n1; if (axisn < (unsigned int) PUZZLE_MIN_SIZE_FOR_CROPPING || axiso < (unsigned int) PUZZLE_MIN_SIZE_FOR_CROPPING) { return 1; } sizeof_chunk_contrasts = chunk_n1 + 1U; if ((chunk_contrasts = calloc(sizeof_chunk_contrasts, sizeof *chunk_contrasts)) == NULL) { return -1; } maptr = view->map; if (axisn >= INT_MAX || axiso >= INT_MAX) { puzzle_err_bug(__FILE__, __LINE__); } if (INT_MAX / axisn < axiso) { puzzle_err_bug(__FILE__, __LINE__); } chunk_n = chunk_n1; do { chunk_contrast = 0.0; chunk_o = chunk_o1; previous_level = *maptr; do { level = *maptr; if (previous_level > level) { chunk_contrast += (double) (previous_level - level); } else { chunk_contrast += (double) (level - previous_level); } previous_level = level; maptr += omaptrinc; } while (chunk_o-- != 0U); chunk_contrasts[chunk_n] = chunk_contrast; total_contrast += chunk_contrast; maptr += nmaptrinc; } while (chunk_n-- != 0U); barrier_contrast = total_contrast * context->puzzle_contrast_barrier_for_cropping; total_contrast = 0.0; *crop0 = 0U; do { total_contrast += chunk_contrasts[*crop0]; if (total_contrast >= barrier_contrast) { break; } } while ((*crop0)++ < chunk_n1); total_contrast = 0.0; *crop1 = chunk_n1; do { total_contrast += chunk_contrasts[*crop1]; if (total_contrast >= barrier_contrast) { break; } } while ((*crop1)-- > 0U); free(chunk_contrasts); if (*crop0 > chunk_n1 || *crop1 > chunk_n1) { puzzle_err_bug(__FILE__, __LINE__); } max_crop = (unsigned int) round((double) chunk_n1 * context->puzzle_max_cropping_ratio); if (max_crop > chunk_n1) { puzzle_err_bug(__FILE__, __LINE__); } *crop0 = MIN(*crop0, max_crop); *crop1 = MAX(*crop1, chunk_n1 - max_crop); return 0; } static int puzzle_autocrop_view(PuzzleContext * context, PuzzleView * const view) { unsigned int cropx0, cropx1; unsigned int cropy0, cropy1; unsigned int x, y; unsigned char *maptr; if (puzzle_autocrop_axis(context, view, &cropx0, &cropx1, view->width, view->height, (int) view->width, 1 - (int) (view->width * view->height)) < 0 || puzzle_autocrop_axis(context, view, &cropy0, &cropy1, view->height, view->width, 1, 0) < 0) { return -1; } if (cropx0 > cropx1 || cropy0 > cropy1) { puzzle_err_bug(__FILE__, __LINE__); } maptr = view->map; y = cropy0; do { x = cropx0; do { *maptr++ = PUZZLE_VIEW_PIXEL(view, x, y); } while (x++ != cropx1); } while (y++ != cropy1); view->width = cropx1 - cropx0 + 1U; view->height = cropy1 - cropy0 + 1U; view->sizeof_map = (size_t) view->width * (size_t) view->height; if (view->width <= 0U || view->height <= 0U || SIZE_MAX / view->width < view->height) { puzzle_err_bug(__FILE__, __LINE__); } return 0; } static int puzzle_getview_from_gdimage(PuzzleContext * const context, PuzzleView * const view, gdImagePtr gdimage) { unsigned int x, y; const unsigned int x0 = 0U, y0 = 0U; unsigned int x1, y1; unsigned char *maptr; int pixel; view->map = NULL; view->width = (unsigned int) gdImageSX(gdimage); view->height = (unsigned int) gdImageSY(gdimage); view->sizeof_map = (size_t) (view->width * view->height); if (view->width > context->puzzle_max_width || view->height > context->puzzle_max_height) { return -1; } if (view->sizeof_map <= (size_t) 0U || INT_MAX / view->width < view->height || SIZE_MAX / view->width < view->height || (unsigned int) view->sizeof_map != view->sizeof_map) { puzzle_err_bug(__FILE__, __LINE__); } x1 = view->width - 1U; y1 = view->height - 1U; if (view->width <= 0U || view->height <= 0U) { puzzle_err_bug(__FILE__, __LINE__); } if ((view->map = calloc(view->sizeof_map, sizeof *view->map)) == NULL) { return -1; } if (x1 > INT_MAX || y1 > INT_MAX) { /* GD uses "int" for coordinates */ puzzle_err_bug(__FILE__, __LINE__); } maptr = view->map; x = x1; if (gdImageTrueColor(gdimage) != 0) { do { y = y1; do { pixel = gdImageGetTrueColorPixel(gdimage, (int) x, (int) y); *maptr++ = (unsigned char) ((gdTrueColorGetRed(pixel) * 77 + gdTrueColorGetGreen(pixel) * 151 + gdTrueColorGetBlue(pixel) * 28 + 128) / 256); } while (y-- != y0); } while (x-- != x0); } else { do { y = y1; do { pixel = gdImagePalettePixel(gdimage, x, y); *maptr++ = (unsigned char) ((gdimage->red[pixel] * 77 + gdimage->green[pixel] * 151 + gdimage->blue[pixel] * 28 + 128) / 256); } while (y-- != y0); } while (x-- != x0); } return 0; } static double puzzle_softedgedlvl(const PuzzleView * const view, const unsigned int x, const unsigned int y) { unsigned int lvl = 0U; unsigned int ax, ay; unsigned int count = 0U; const unsigned int xlimit = x + PUZZLE_PIXEL_FUZZ_SIZE; const unsigned int ylimit = y + PUZZLE_PIXEL_FUZZ_SIZE; if (x >= view->width || y >= view->height || xlimit <= x || ylimit <= y) { puzzle_err_bug(__FILE__, __LINE__); } if (x > PUZZLE_PIXEL_FUZZ_SIZE) { ax = x - PUZZLE_PIXEL_FUZZ_SIZE; } else { ax = 0U; } do { if (ax >= view->width) { break; } if (y > PUZZLE_PIXEL_FUZZ_SIZE) { ay = y - PUZZLE_PIXEL_FUZZ_SIZE; } else { ay = 0U; } do { if (ay >= view->height) { break; } count++; lvl += (unsigned int) PUZZLE_VIEW_PIXEL(view, ax, ay); } while (ay++ < ylimit); } while (ax++ < xlimit); if (count <= 0U) { return 0.0; } return (double) lvl / (double) count; } static double puzzle_get_avglvl(const PuzzleView * const view, const unsigned int x, const unsigned int y, const unsigned int width, const unsigned int height) { double lvl = 0.0; const unsigned int xlimit = x + width - 1U; const unsigned int ylimit = y + height - 1U; unsigned int ax, ay; if (width <= 0U || height <= 0U) { puzzle_err_bug(__FILE__, __LINE__); } if (xlimit < x || ylimit < y) { puzzle_err_bug(__FILE__, __LINE__); } ax = x; do { if (ax >= view->width) { puzzle_err_bug(__FILE__, __LINE__); } ay = y; do { if (ay >= view->height) { puzzle_err_bug(__FILE__, __LINE__); } lvl += puzzle_softedgedlvl(view, ax, ay); } while (ay++ < ylimit); } while (ax++ < xlimit); return lvl / (double) (width * height); } static int puzzle_fill_avglgls(PuzzleContext * const context, PuzzleAvgLvls * const avglvls, const PuzzleView * const view, const unsigned int lambdas) { double width = (double) view->width; double height = (double) view->height; double xshift, yshift; double x, y; unsigned int p; unsigned int lx, ly; unsigned int xd, yd; unsigned int px, py; unsigned int lwidth, lheight; double avglvl; avglvls->lambdas = lambdas; avglvls->sizeof_lvls = (size_t) lambdas * lambdas; if (UINT_MAX / lambdas < lambdas || (unsigned int) avglvls->sizeof_lvls != avglvls->sizeof_lvls) { puzzle_err_bug(__FILE__, __LINE__); } if ((avglvls->lvls = calloc(avglvls->sizeof_lvls, sizeof *avglvls->lvls)) == NULL) { return -1; } xshift = (width - (width * (double) lambdas / (double) SUCC(lambdas))) / 2.0; yshift = (height - (height * (double) lambdas / (double) SUCC(lambdas))) / 2.0; p = (unsigned int) round(MIN(width, height) / (SUCC(lambdas) * context->puzzle_p_ratio)); if (p < PUZZLE_MIN_P) { p = PUZZLE_MIN_P; } lx = 0U; do { ly = 0U; do { x = xshift + (double) lx * PRED(width) / SUCC(lambdas); y = yshift + (double) ly * PRED(height) / SUCC(lambdas); lwidth = (unsigned int) round (xshift + (double) SUCC(lx) * PRED(width) / (double) SUCC(lambdas) - x); lheight = (unsigned int) round (yshift + (double) SUCC(ly) * PRED(height) / (double) SUCC(lambdas) - y); if (p < lwidth) { xd = (unsigned int) round(x + (lwidth - p) / 2.0); } else { xd = (unsigned int) round(x); } if (p < lheight) { yd = (unsigned int) round(y + (lheight - p) / 2.0); } else { yd = (unsigned int) round(y); } if (view->width - xd < p) { px = 1U; } else { px = p; } if (view->height - yd < p) { py = 1U; } else { py = p; } if (px > 0U && py > 0U) { avglvl = puzzle_get_avglvl(view, xd, yd, px, py); } else { avglvl = 0.0; } PUZZLE_AVGLVL(avglvls, lx, ly) = avglvl; } while (++ly < lambdas); } while (++lx < lambdas); return 0; } static unsigned int puzzle_add_neighbors(double ** const vecur, const unsigned int max_neighbors, const PuzzleAvgLvls * const avglvls, const unsigned int lx, const unsigned int ly) { unsigned int ax, ay; unsigned int xlimit, ylimit; unsigned int neighbors = 0U; const double ref = PUZZLE_AVGLVL(avglvls, lx, ly); if (max_neighbors != 8U) { puzzle_err_bug(__FILE__, __LINE__); } if (lx >= avglvls->lambdas - 1U) { xlimit = avglvls->lambdas - 1U; } else { xlimit = lx + 1U; } if (ly >= avglvls->lambdas - 1U) { ylimit = avglvls->lambdas - 1U; } else { ylimit = ly + 1U; } if (lx <= 0U) { ax = 0U; } else { ax = lx - 1U; } do { if (ly <= 0U) { ay = 0U; } else { ay = ly - 1U; } do { if (ax == lx && ay == ly) { continue; } *(*vecur)++ = ref - PUZZLE_AVGLVL(avglvls, ax, ay); neighbors++; if (neighbors <= 0U) { puzzle_err_bug(__FILE__, __LINE__); } } while (ay++ < ylimit); } while (ax++ < xlimit); if (neighbors > max_neighbors) { puzzle_err_bug(__FILE__, __LINE__); } return neighbors; } static int puzzle_fill_dvec(PuzzleDvec * const dvec, const PuzzleAvgLvls * const avglvls) { unsigned int lambdas; unsigned int lx, ly; double *vecur; lambdas = avglvls->lambdas; dvec->sizeof_compressed_vec = (size_t) 0U; dvec->sizeof_vec = (size_t) (lambdas * lambdas * PUZZLE_NEIGHBORS); if (SIZE_MAX / ((size_t) (lambdas * lambdas)) < (size_t) PUZZLE_NEIGHBORS || (unsigned int) dvec->sizeof_vec != dvec->sizeof_vec) { puzzle_err_bug(__FILE__, __LINE__); } if ((dvec->vec = calloc(dvec->sizeof_vec, sizeof *dvec->vec)) == NULL) { return -1; } vecur = dvec->vec; lx = 0U; do { ly = 0U; do { (void) puzzle_add_neighbors(&vecur, PUZZLE_NEIGHBORS, avglvls, lx, ly); } while (++ly < lambdas); } while (++lx < lambdas); dvec->sizeof_compressed_vec = (size_t) (vecur - dvec->vec); return 0; } static void puzzle_remove_transparency(gdImagePtr gdimage) { int background = gdTrueColor(255, 255, 255); int x, y, cpix; gdImagePaletteToTrueColor(gdimage); for (y = 0; y < gdImageSY(gdimage); y++) { for (x = 0; x < gdImageSX(gdimage); x++) { cpix = gdImageGetTrueColorPixel(gdimage, x, y); gdImageSetPixel(gdimage, x, y, gdAlphaBlend(background, cpix)); } } } static gdImagePtr puzzle_create_gdimage_from_file(const char * const file) { gdImagePtr gdimage = NULL; FILE *fp; PuzzleImageTypeCode image_type_code; if ((fp = fopen(file, "rb")) == NULL) { return NULL; } image_type_code = puzzle_get_image_type_from_fp(fp); switch (image_type_code) { case PUZZLE_IMAGE_TYPE_JPEG: gdimage = gdImageCreateFromJpeg(fp); break; case PUZZLE_IMAGE_TYPE_PNG: gdimage = gdImageCreateFromPng(fp); break; case PUZZLE_IMAGE_TYPE_GIF: gdimage = gdImageCreateFromGif(fp); break; default: gdimage = NULL; } (void) fclose(fp); return gdimage; } static gdImagePtr puzzle_create_gdimage_from_mem(const void * const mem, const size_t size) { gdImagePtr gdimage = NULL; PuzzleImageTypeCode image_type_code = puzzle_get_image_type_from_header(mem); switch (image_type_code) { case PUZZLE_IMAGE_TYPE_JPEG: gdimage = gdImageCreateFromJpegPtr(size, (void *)mem); break; case PUZZLE_IMAGE_TYPE_PNG: gdimage = gdImageCreateFromPngPtr(size, (void *)mem); break; case PUZZLE_IMAGE_TYPE_GIF: gdimage = gdImageCreateFromGifPtr(size, (void *)mem); break; default: gdimage = NULL; } return gdimage; } static int puzzle_fill_dvec_from_gdimage(PuzzleContext * const context, PuzzleDvec * const dvec, const gdImagePtr gdimage) { PuzzleView view; PuzzleAvgLvls avglvls; int ret = 0; if (context->magic != PUZZLE_CONTEXT_MAGIC) { puzzle_err_bug(__FILE__, __LINE__); } puzzle_init_view(&view); puzzle_init_avglvls(&avglvls); puzzle_init_dvec(context, dvec); ret = puzzle_getview_from_gdimage(context, &view, gdimage); if (ret != 0) { goto out; } if (context->puzzle_enable_autocrop != 0 && (ret = puzzle_autocrop_view(context, &view)) < 0) { goto out; } if ((ret = puzzle_fill_avglgls(context, &avglvls, &view, context->puzzle_lambdas)) != 0) { goto out; } ret = puzzle_fill_dvec(dvec, &avglvls); out: puzzle_free_view(&view); puzzle_free_avglvls(&avglvls); return ret; } int puzzle_fill_dvec_from_file(PuzzleContext * const context, PuzzleDvec * const dvec, const char * const file) { int ret; gdImagePtr gdimage = puzzle_create_gdimage_from_file(file); if (gdimage == NULL) { return -1; } puzzle_remove_transparency(gdimage); ret = puzzle_fill_dvec_from_gdimage(context, dvec, gdimage); gdImageDestroy(gdimage); return ret; } int puzzle_fill_dvec_from_mem(PuzzleContext * const context, PuzzleDvec * const dvec, const void * const mem, const size_t size) { int ret; gdImagePtr gdimage = puzzle_create_gdimage_from_mem(mem, size); if (gdimage == NULL) { return -1; } puzzle_remove_transparency(gdimage); ret = puzzle_fill_dvec_from_gdimage(context, dvec, gdimage); gdImageDestroy(gdimage); return ret; } int puzzle_dump_dvec(PuzzleContext * const context, const PuzzleDvec * const dvec) { size_t s = dvec->sizeof_compressed_vec; const double *vecptr = dvec->vec; (void) context; if (s <= (size_t) 0U) { puzzle_err_bug(__FILE__, __LINE__); } do { printf("%g\n", *vecptr++); } while (--s != (size_t) 0U); return 0; }