/* Miscelaneous image manipulations Copyright (C) 2001 by Andrew Zabolotny */ #include "image.h" #include #include #include #include #ifdef _MSC_VER #define _USE_MATH_DEFINES #include float rint (float x) { return floor (x + 0.5f); } float trunc (float x) { return (((x) < 0) ? ceil ((x)) : floor ((x))); } #define unlink _unlink #else #include #include #endif // A support of 3 gives an overall sharper looking image, but // it is a) slower b) gives more sharpening artefacts #define LANCZOS_SUPPORT 2 // Lanczos kernel is precomputed in a table with this resolution // The value below seems to be enough for HQ upscaling up to eight times #define LANCZOS_TABLE_RES 256 template static inline T square (T x) { return x * x; } float *Image::lanczos_func = NULL; int Image::lanczos_func_use = 0; Image::Image () : file (NULL), lanczos_func_in_use (false), image (NULL) { } Image::~Image () { Close (); Free (); } bool Image::Open (const char *fName) { file = fopen (fName, "rb"); if (!file) return false; fseek (file, 0, SEEK_END); filesize = ftell (file); fseek (file, 0, SEEK_SET); return true; } void Image::Close () { if (file) { fclose (file); file = NULL; } } void Image::Free () { delete [] image; image = NULL; if (lanczos_func_in_use && !--lanczos_func_use) { delete [] lanczos_func; lanczos_func = NULL; lanczos_func_in_use = false; } } bool Image::LoadPNG () { png_structp png; png_infop info; size_t rowbytes, exp_rowbytes; png_bytep *row_pointers; Free (); png = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) return false; info = png_create_info_struct (png); if (!info) { png_destroy_read_struct (&png, (png_infopp) NULL, (png_infopp) NULL); png = NULL; return false; } png_init_io (png, file); if (setjmp (png_jmpbuf(png))) // If we get here, we had a problem reading the file goto nomem; png_read_info (png, info); // Get picture info png_uint_32 Width, Height; int bit_depth, color_type; png_get_IHDR (png, info, &Width, &Height, &bit_depth, &color_type, NULL, NULL, NULL); if (bit_depth > 8) // tell libpng to strip 16 bit/color files down to 8 bits/color png_set_strip_16 (png); else if (bit_depth < 8) // Expand pictures with less than 8bpp to 8bpp png_set_packing (png); switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: png_set_gray_to_rgb (png); break; case PNG_COLOR_TYPE_PALETTE: png_set_palette_to_rgb (png); break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: break; default: goto nomem; } // If there is no alpha information, fill with 0xff if (!(color_type & PNG_COLOR_MASK_ALPHA)) { // Expand paletted or RGB images with transparency to full alpha // channels so the data will be available as RGBA quartets. if (png_get_valid (png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha (png); else png_set_filler (png, 0xff, PNG_FILLER_AFTER); } // Update structure with the above settings png_read_update_info (png, info); // Allocate the memory to hold the image image = new RGBpixel [(width = Width) * (height = Height)]; if (!image) goto nomem; exp_rowbytes = Width * sizeof (RGBpixel); rowbytes = png_get_rowbytes (png, info); if (rowbytes != exp_rowbytes) goto nomem; // Yuck! Something went wrong! row_pointers = new png_bytep [Height]; if (!row_pointers || setjmp (png_jmpbuf(png))) // Set a new exception handler { delete [] row_pointers; nomem: png_destroy_read_struct (&png, &info, (png_infopp) NULL); Free (); return false; } for (png_uint_32 row = 0; row < Height; row++) row_pointers [row] = ((png_bytep)image) + row * rowbytes; // Read image data png_read_image (png, row_pointers); // read rest of file, and get additional chunks in info_ptr png_read_end (png, (png_infop)NULL); // Free the row pointers array that is not needed anymore delete [] row_pointers; png_destroy_read_struct (&png, &info, (png_infopp) NULL); return true; } static inline int isqr (int x) { return x * x; } bool Image::SavePNG (const char *fName) { /* Remove the file in the case it exists and it is a link */ unlink (fName); /* open the file */ FILE *fp = fopen (fName, "wb"); if (fp == NULL) return false; png_structp png = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) { error1: fclose (fp); return false; } /* Allocate/initialize the image information data. */ png_infop info = png_create_info_struct (png); if (info == NULL) { error2: png_destroy_write_struct (&png, &info); goto error1; } /* Catch processing errors */ if (setjmp(png_jmpbuf(png))) /* If we get here, we had a problem writing the file */ goto error2; /* Set up the output function */ png_init_io (png, fp); /* Set the image information here. Width and height are up to 2^31, * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED */ int colortype, rowlen, bits; colortype = PNG_COLOR_TYPE_RGB_ALPHA; rowlen = width * sizeof (RGBpixel); bits = 8; png_set_IHDR (png, info, width, height, bits, colortype, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); /* if we are dealing with a color image then */ png_color_8 sig_bit; memset (&sig_bit, 0, sizeof (sig_bit)); sig_bit.red = 8; sig_bit.green = 8; sig_bit.blue = 8; /* if the image has an alpha channel then */ if (colortype & PNG_COLOR_MASK_ALPHA) sig_bit.alpha = bits; png_set_sBIT (png, info, &sig_bit); /* Write the file header information. */ png_write_info (png, info); /* Get rid of filler (OR ALPHA) bytes, pack XRGB/RGBX/ARGB/RGBA into * RGB (4 channels -> 3 channels). The second parameter is not used. */ if (!(colortype & PNG_COLOR_MASK_ALPHA)) png_set_filler (png, 0, PNG_FILLER_AFTER); /* The easiest way to write the image (you may have a different memory * layout, however, so choose what fits your needs best). You need to * use the first method if you aren't handling interlacing yourself. */ png_bytep *row_pointers = new png_bytep [height]; unsigned char *ImageData = (unsigned char *)image; for (unsigned i = 0; i < height; i++) row_pointers [i] = ImageData + i * rowlen; /* One of the following output methods is REQUIRED */ png_write_image (png, row_pointers); /* It is REQUIRED to call this to finish writing the rest of the file */ png_write_end (png, info); /* clean up after the write, and free any memory allocated */ png_destroy_write_struct (&png, &info); /* Free the row pointers */ delete [] row_pointers; /* close the file */ fclose (fp); /* that's it */ return true; } void Image::Resize (unsigned newwidth, unsigned newheight) { Free (); image = new RGBpixel [(width = newwidth) * (height = newheight)]; } void Image::InitInterpolation (InterpolationMethod method) { switch (method) { case I_NEAREST: fGetR = GetR_n; fGetG = GetG_n; fGetB = GetB_n; fGet = Get_n; break; case I_BILINEAR: fGetR = GetR_b; fGetG = GetG_b; fGetB = GetB_b; fGet = Get_b; break; case I_LANCZOS: fGetR = GetR_l; fGetG = GetG_l; fGetB = GetB_l; fGet = Get_l; if (!lanczos_func) { // Precompute the function for faster interpolation lanczos_func = new float [LANCZOS_SUPPORT * LANCZOS_SUPPORT * LANCZOS_TABLE_RES]; for (int i = 0; i < LANCZOS_SUPPORT * LANCZOS_SUPPORT * LANCZOS_TABLE_RES; i++) { float d = sqrt (float (i) / LANCZOS_TABLE_RES); if (d == 0.0) lanczos_func [i] = 1.0; else lanczos_func [i] = (LANCZOS_SUPPORT * sin (M_PI * d) * sin ((M_PI / LANCZOS_SUPPORT) * d)) / (M_PI * M_PI * d * d); } } if (!lanczos_func_in_use) { lanczos_func_in_use = true; lanczos_func_use++; } break; } } // --- // Nearest interpolation // --- // unsigned char Image::GetR_n (Image *This, float x, float y) { unsigned xi = unsigned (x + 0.5); unsigned yi = unsigned (y + 0.5); if (xi >= This->width || yi >= This->height) return 0; RGBpixel *p = This->image + yi * This->width + xi; return p->red; } unsigned char Image::GetG_n (Image *This, float x, float y) { unsigned xi = unsigned (x + 0.5); unsigned yi = unsigned (y + 0.5); if (xi >= This->width || yi >= This->height) return 0; RGBpixel *p = This->image + yi * This->width + xi; return p->green; } unsigned char Image::GetB_n (Image *This, float x, float y) { unsigned xi = unsigned (x + 0.5); unsigned yi = unsigned (y + 0.5); if (xi >= This->width || yi >= This->height) return 0; RGBpixel *p = This->image + yi * This->width + xi; return p->blue; } void Image::Get_n (Image *This, RGBpixel &out, float x, float y) { unsigned xi = unsigned (x + 0.5); unsigned yi = unsigned (y + 0.5); if (xi >= This->width || yi >= This->height) return; RGBpixel *p = This->image + yi * This->width + xi; out = *p; } // --- // Bi-linear interpolation // --- // unsigned char Image::GetR_b (Image *This, float x, float y) { // linear interpolation unsigned xi = unsigned (x); unsigned yi = unsigned (y); if (xi >= This->width || yi >= This->height) return 0; unsigned dx = unsigned ((x - trunc (x)) * 256); unsigned dy = unsigned ((y - trunc (y)) * 256); RGBpixel *p0 = This->image + yi * This->width + xi; RGBpixel *p1 = p0 + This->width; unsigned k1, k2; k1 = 256 * p0 [0].red + dx * (int (p0 [1].red ) - int (p0 [0].red )); k2 = 256 * p1 [0].red + dx * (int (p1 [1].red ) - int (p1 [0].red )); return (256 * k1 + dy * (k2 - k1)) >> 16; } unsigned char Image::GetG_b (Image *This, float x, float y) { // linear interpolation unsigned xi = int (x); unsigned yi = int (y); if (xi >= This->width || yi >= This->height) return 0; unsigned dx = unsigned ((x - trunc (x)) * 256); unsigned dy = unsigned ((y - trunc (y)) * 256); RGBpixel *p0 = This->image + yi * This->width + xi; RGBpixel *p1 = p0 + This->width; unsigned k1, k2; k1 = 256 * p0 [0].green + dx * (int (p0 [1].green) - int (p0 [0].green)); k2 = 256 * p1 [0].green + dx * (int (p1 [1].green) - int (p1 [0].green)); return (256 * k1 + dy * (k2 - k1)) >> 16; } unsigned char Image::GetB_b (Image *This, float x, float y) { // linear interpolation unsigned xi = int (x); unsigned yi = int (y); if (xi >= This->width || yi >= This->height) return 0; unsigned dx = unsigned ((x - trunc (x)) * 256); unsigned dy = unsigned ((y - trunc (y)) * 256); RGBpixel *p0 = This->image + yi * This->width + xi; RGBpixel *p1 = p0 + This->width; unsigned k1, k2; k1 = 256 * p0 [0].blue + dx * (int (p0 [1].blue ) - int (p0 [0].blue )); k2 = 256 * p1 [0].blue + dx * (int (p1 [1].blue ) - int (p1 [0].blue )); return (256 * k1 + dy * (k2 - k1)) >> 16; } void Image::Get_b (Image *This, RGBpixel &out, float x, float y) { // linear interpolation unsigned xi = unsigned (x); unsigned yi = unsigned (y); if (xi >= This->width || yi >= This->height) { out.red = out.green = out.blue = 0; return; } unsigned dx = unsigned ((x - trunc (x)) * 256); unsigned dy = unsigned ((y - trunc (y)) * 256); RGBpixel *p0 = This->image + yi * This->width + xi; RGBpixel *p1 = p0 + This->width; unsigned k1, k2; k1 = 256 * p0 [0].red + dx * (int (p0 [1].red ) - int (p0 [0].red )); k2 = 256 * p1 [0].red + dx * (int (p1 [1].red ) - int (p1 [0].red )); out.red = (256 * k1 + dy * (k2 - k1)) >> 16; k1 = 256 * p0 [0].green + dx * (int (p0 [1].green) - int (p0 [0].green)); k2 = 256 * p1 [0].green + dx * (int (p1 [1].green) - int (p1 [0].green)); out.green = (256 * k1 + dy * (k2 - k1)) >> 16; k1 = 256 * p0 [0].blue + dx * (int (p0 [1].blue ) - int (p0 [0].blue )); k2 = 256 * p1 [0].blue + dx * (int (p1 [1].blue ) - int (p1 [0].blue )); out.blue = (256 * k1 + dy * (k2 - k1)) >> 16; } // --- // Lanczos 2 interpolation // --- // unsigned char Image::GetR_l (Image *This, float x, float y) { float xs = rint (x) - LANCZOS_SUPPORT; float ys = rint (y) - LANCZOS_SUPPORT; float xe = xs + LANCZOS_SUPPORT * 2; float ye = ys + LANCZOS_SUPPORT * 2; float norm = 0.0; float sum = 0.0; RGBpixel *img = This->image + (long (ys) * This->width + long (xs)); if (xs >= 0 && ys >= 0 && xe < This->width && ye < This->height) for (; ys <= ye; ys += 1.0) { for (float xc = xs; xc <= xe; xc += 1.0, img++) { float d = square (x - xc) + square (y - ys); if (d >= LANCZOS_SUPPORT * LANCZOS_SUPPORT) continue; d = lanczos_func [int (d * LANCZOS_TABLE_RES)]; norm += d; sum += d * img->red; } img += This->width - LANCZOS_SUPPORT * 2 - 1; } else { for (; ys <= ye; ys += 1.0) { if (ys < 0 || ys >= This->height) { img += This->width; continue; } for (float xc = xs; xc <= xe; xc += 1.0, img++) { if (xc < 0 || xc >= This->width) continue; float d = square (x - xc) + square (y - ys); if (d >= LANCZOS_SUPPORT * LANCZOS_SUPPORT) continue; d = lanczos_func [int (d * LANCZOS_TABLE_RES)]; norm += d; sum += d * img->red; } img += This->width - LANCZOS_SUPPORT * 2 - 1; } if (norm == 0.0) return 0; } int r = int (sum / norm); return r > 255 ? 255 : r < 0 ? 0 : r; } unsigned char Image::GetG_l (Image *This, float x, float y) { float xs = rint (x) - LANCZOS_SUPPORT; float ys = rint (y) - LANCZOS_SUPPORT; float xe = xs + LANCZOS_SUPPORT * 2; float ye = ys + LANCZOS_SUPPORT * 2; float norm = 0.0; float sum = 0.0; RGBpixel *img = This->image + (long (ys) * This->width + long (xs)); if (xs >= 0 && ys >= 0 && xe < This->width && ye < This->height) for (; ys <= ye; ys += 1.0) { for (float xc = xs; xc <= xe; xc += 1.0, img++) { float d = square (x - xc) + square (y - ys); if (d >= LANCZOS_SUPPORT * LANCZOS_SUPPORT) continue; d = lanczos_func [int (d * LANCZOS_TABLE_RES)]; norm += d; sum += d * img->green; } img += This->width - LANCZOS_SUPPORT * 2 - 1; } else { for (; ys <= ye; ys += 1.0) { if (ys < 0 || ys >= This->height) { img += This->width; continue; } for (float xc = xs; xc <= xe; xc += 1.0, img++) { if (xc < 0 || xc >= This->width) continue; float d = square (x - xc) + square (y - ys); if (d >= LANCZOS_SUPPORT * LANCZOS_SUPPORT) continue; d = lanczos_func [int (d * LANCZOS_TABLE_RES)]; norm += d; sum += d * img->green; } img += This->width - LANCZOS_SUPPORT * 2 - 1; } if (norm == 0.0) return 0; } int r = int (sum / norm); return r > 255 ? 255 : r < 0 ? 0 : r; } unsigned char Image::GetB_l (Image *This, float x, float y) { float xs = rint (x) - LANCZOS_SUPPORT; float ys = rint (y) - LANCZOS_SUPPORT; float xe = xs + LANCZOS_SUPPORT * 2; float ye = ys + LANCZOS_SUPPORT * 2; float norm = 0.0; float sum = 0.0; RGBpixel *img = This->image + (long (ys) * This->width + long (xs)); if (xs >= 0 && ys >= 0 && xe < This->width && ye < This->height) for (; ys <= ye; ys += 1.0) { for (float xc = xs; xc <= xe; xc += 1.0, img++) { float d = square (x - xc) + square (y - ys); if (d >= LANCZOS_SUPPORT * LANCZOS_SUPPORT) continue; d = lanczos_func [int (d * LANCZOS_TABLE_RES)]; norm += d; sum += d * img->blue; } img += This->width - LANCZOS_SUPPORT * 2 - 1; } else { for (; ys <= ye; ys += 1.0) { if (ys < 0 || ys >= This->height) { img += This->width; continue; } for (float xc = xs; xc <= xe; xc += 1.0, img++) { if (xc < 0 || xc >= This->width) continue; float d = square (x - xc) + square (y - ys); if (d >= LANCZOS_SUPPORT * LANCZOS_SUPPORT) continue; d = lanczos_func [int (d * LANCZOS_TABLE_RES)]; norm += d; sum += d * img->blue; } img += This->width - LANCZOS_SUPPORT * 2 - 1; } if (norm == 0.0) return 0; } int r = int (sum / norm); return r > 255 ? 255 : r < 0 ? 0 : r; } void Image::Get_l (Image *This, RGBpixel &out, float x, float y) { float xs = rint (x) - LANCZOS_SUPPORT; float ys = rint (y) - LANCZOS_SUPPORT; float xe = xs + LANCZOS_SUPPORT * 2; float ye = ys + LANCZOS_SUPPORT * 2; float norm = 0.0; float sumR = 0.0; float sumG = 0.0; float sumB = 0.0; RGBpixel *img = This->image + (long (ys) * This->width + long (xs)); if (xs >= 0 && ys >= 0 && xe < This->width && ye < This->height) for (; ys <= ye; ys += 1.0) { for (float xc = xs; xc <= xe; xc += 1.0, img++) { float d = square (x - xc) + square (y - ys); if (d >= LANCZOS_SUPPORT * LANCZOS_SUPPORT) continue; d = lanczos_func [int (d * LANCZOS_TABLE_RES)]; norm += d; sumR += d * img->red; sumG += d * img->green; sumB += d * img->blue; } img += This->width - LANCZOS_SUPPORT * 2 - 1; } else { for (; ys <= ye; ys += 1.0) { if (ys < 0 || ys >= This->height) { img += This->width; continue; } for (float xc = xs; xc <= xe; xc += 1.0, img++) { if (xc < 0 || xc >= This->width) continue; float d = square (x - xc) + square (y - ys); if (d >= LANCZOS_SUPPORT * LANCZOS_SUPPORT) continue; d = lanczos_func [int (d * LANCZOS_TABLE_RES)]; norm += d; sumR += d * img->red; sumG += d * img->green; sumB += d * img->blue; } img += This->width - LANCZOS_SUPPORT * 2 - 1; } if (norm == 0.0) { out.Set (0, 0, 0); return; } } int rR = int (sumR / norm); int rG = int (sumG / norm); int rB = int (sumB / norm); out.Set (rR > 255 ? 255 : rR < 0 ? 0 : rR, rG > 255 ? 255 : rG < 0 ? 0 : rG, rB > 255 ? 255 : rB < 0 ? 0 : rB); }