// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** rendutil.cpp Core rendering utilities. ***************************************************************************/ #include "emu.h" #include "rendutil.h" #include "msdib.h" #include "png.h" #include "jpeglib.h" #include "jerror.h" #include #include #include namespace { struct jpeg_corefile_source : public jpeg_source_mgr { static void source(j_decompress_ptr cinfo, util::random_read &file) noexcept; private: static constexpr unsigned INPUT_BUF_SIZE = 4096; static void do_init(j_decompress_ptr cinfo) noexcept { jpeg_corefile_source &src = *static_cast(cinfo->src); src.start_of_file = true; } static boolean do_fill(j_decompress_ptr cinfo) noexcept { jpeg_corefile_source &src = *static_cast(cinfo->src); auto [err, nbytes] = read(*src.infile, src.buffer, INPUT_BUF_SIZE); // TODO: check error return if (0 >= nbytes) { if (src.start_of_file) ERREXIT(cinfo, JERR_INPUT_EMPTY); WARNMS(cinfo, JWRN_JPEG_EOF); src.buffer[0] = JOCTET(0xff); src.buffer[1] = JOCTET(JPEG_EOI); nbytes = 2; } src.next_input_byte = src.buffer; src.bytes_in_buffer = nbytes; src.start_of_file = false; return TRUE; } static void do_skip(j_decompress_ptr cinfo, long num_bytes) noexcept { jpeg_corefile_source &src = *static_cast(cinfo->src); if (0 < num_bytes) { while (long(src.bytes_in_buffer) < num_bytes) { num_bytes -= long(src.bytes_in_buffer); std::ignore = (*src.fill_input_buffer)(cinfo); } src.next_input_byte += size_t(num_bytes); src.bytes_in_buffer -= size_t(num_bytes); } } static void do_term(j_decompress_ptr cinfo) noexcept { } util::random_read *infile; JOCTET *buffer; bool start_of_file; }; void jpeg_corefile_source::source(j_decompress_ptr cinfo, util::random_read &file) noexcept { jpeg_corefile_source *src; if (!cinfo->src) { src = reinterpret_cast( (*cinfo->mem->alloc_small)( reinterpret_cast(cinfo), JPOOL_PERMANENT, sizeof(jpeg_corefile_source))); cinfo->src = src; src->buffer = reinterpret_cast( (*cinfo->mem->alloc_small)( reinterpret_cast(cinfo), JPOOL_PERMANENT, INPUT_BUF_SIZE * sizeof(JOCTET))); } else { src = static_cast(cinfo->src); } src->init_source = &jpeg_corefile_source::do_init; src->fill_input_buffer = &jpeg_corefile_source::do_fill; src->skip_input_data = &jpeg_corefile_source::do_skip; src->resync_to_restart = jpeg_resync_to_restart; src->term_source = &jpeg_corefile_source::do_term; src->infile = &file; src->bytes_in_buffer = 0; src->next_input_byte = nullptr; } struct jpeg_setjmp_error_mgr : public jpeg_error_mgr { jpeg_setjmp_error_mgr() noexcept { jpeg_std_error(this); error_exit = [] (j_common_ptr cinfo) { std::longjmp(static_cast(cinfo->err)->m_jump_buffer, 1); }; } std::jmp_buf m_jump_buffer; }; } // anonymous namespace /*************************************************************************** FUNCTION PROTOTYPES ***************************************************************************/ /* utilities */ static void resample_argb_bitmap_average(u32 *dest, u32 drowpixels, u32 dwidth, u32 dheight, const u32 *source, u32 srowpixels, u32 swidth, u32 sheight, const render_color &color, u32 dx, u32 dy) noexcept; static void resample_argb_bitmap_bilinear(u32 *dest, u32 drowpixels, u32 dwidth, u32 dheight, const u32 *source, u32 srowpixels, u32 swidth, u32 sheight, const render_color &color, u32 dx, u32 dy) noexcept; static bool copy_png_alpha_to_bitmap(bitmap_argb32 &bitmap, const util::png_info &png) noexcept; /*************************************************************************** RENDER UTILITIES ***************************************************************************/ /*------------------------------------------------- render_resample_argb_bitmap_hq - perform a high quality resampling of a texture -------------------------------------------------*/ void render_resample_argb_bitmap_hq(bitmap_argb32 &dest, bitmap_argb32 &source, const render_color &color, bool force) noexcept { if (dest.width() == 0 || dest.height() == 0) return; // adjust the source base const u32 *sbase = &source.pix(0); // determine the steppings u32 swidth = source.width(); u32 sheight = source.height(); u32 dwidth = dest.width(); u32 dheight = dest.height(); u32 dx = (swidth << 12) / dwidth; u32 dy = (sheight << 12) / dheight; // if the source is higher res than the target, use full averaging if (dx > 0x1000 || dy > 0x1000 || force) resample_argb_bitmap_average(&dest.pix(0), dest.rowpixels(), dwidth, dheight, sbase, source.rowpixels(), swidth, sheight, color, dx, dy); else resample_argb_bitmap_bilinear(&dest.pix(0), dest.rowpixels(), dwidth, dheight, sbase, source.rowpixels(), swidth, sheight, color, dx, dy); } /*------------------------------------------------- resample_argb_bitmap_average - resample a texture by performing a true weighted average over all contributing pixels -------------------------------------------------*/ static void resample_argb_bitmap_average(u32 *dest, u32 drowpixels, u32 dwidth, u32 dheight, const u32 *source, u32 srowpixels, u32 swidth, u32 sheight, const render_color &color, u32 dx, u32 dy) noexcept { u64 const sumscale = u64(dx) * u64(dy); // precompute premultiplied R/G/B/A factors u32 const r = color.r * color.a * 256.0f; u32 const g = color.g * color.a * 256.0f; u32 const b = color.b * color.a * 256.0f; u32 const a = color.a * 256.0f; // loop over the target vertically for (u32 y = 0; y < dheight; y++) { u32 const starty = y * dy; // loop over the target horizontally for (u32 x = 0; x < dwidth; x++) { u64 sumr = 0, sumg = 0, sumb = 0, suma = 0; u32 const startx = x * dx; u32 yremaining = dy; u32 ychunk; // accumulate all source pixels that contribute to this pixel for (u32 cury = starty; yremaining; cury += ychunk) { u32 xremaining = dx; u32 xchunk; // determine the Y contribution, clamping to the amount remaining ychunk = 0x1000 - (cury & 0xfff); if (ychunk > yremaining) ychunk = yremaining; yremaining -= ychunk; // loop over all source pixels in the X direction for (u32 curx = startx; xremaining; curx += xchunk) { // determine the X contribution, clamping to the amount remaining xchunk = 0x1000 - (curx & 0xfff); if (xchunk > xremaining) xchunk = xremaining; xremaining -= xchunk; // total contribution = x * y u32 const factor = xchunk * ychunk; // fetch the source pixel rgb_t const pix = source[(cury >> 12) * srowpixels + (curx >> 12)]; // accumulate the RGBA values sumr += factor * pix.r(); sumg += factor * pix.g(); sumb += factor * pix.b(); suma += factor * pix.a(); } } // apply scaling suma = (suma / sumscale) * a / 256; sumr = (sumr / sumscale) * r / 256; sumg = (sumg / sumscale) * g / 256; sumb = (sumb / sumscale) * b / 256; // if we're translucent, add in the destination pixel contribution if (a < 256) { rgb_t const dpix = dest[y * drowpixels + x]; suma += dpix.a() * (256 - a); sumr += dpix.r() * (256 - a); sumg += dpix.g() * (256 - a); sumb += dpix.b() * (256 - a); } // store the target pixel, dividing the RGBA values by the overall scale factor dest[y * drowpixels + x] = rgb_t(suma, sumr, sumg, sumb); } } } /*------------------------------------------------- resample_argb_bitmap_bilinear - perform texture sampling via a bilinear filter -------------------------------------------------*/ static void resample_argb_bitmap_bilinear(u32 *dest, u32 drowpixels, u32 dwidth, u32 dheight, const u32 *source, u32 srowpixels, u32 swidth, u32 sheight, const render_color &color, u32 dx, u32 dy) noexcept { u32 const maxx = swidth << 12, maxy = sheight << 12; // precompute premultiplied R/G/B/A factors u32 const r = color.r * color.a * 256.0f; u32 const g = color.g * color.a * 256.0f; u32 const b = color.b * color.a * 256.0f; u32 const a = color.a * 256.0f; // loop over the target vertically for (u32 y = 0; y < dheight; y++) { u32 const starty = y * dy; // loop over the target horizontally for (u32 x = 0; x < dwidth; x++) { u32 const startx = x * dx; // adjust start to the center; note that this math will tend to produce // negative results on the first pixel, which is why we clamp below u32 curx = startx + dx / 2 - 0x800; u32 cury = starty + dy / 2 - 0x800; // compute the neighboring pixel u32 const nextx = curx + 0x1000; u32 const nexty = cury + 0x1000; // fetch the four relevant pixels rgb_t pix0 = 0, pix1 = 0, pix2 = 0, pix3 = 0; if (s32(cury) >= 0 && cury < maxy && s32(curx) >= 0 && curx < maxx) pix0 = source[(cury >> 12) * srowpixels + (curx >> 12)]; if (s32(cury) >= 0 && cury < maxy && s32(nextx) >= 0 && nextx < maxx) pix1 = source[(cury >> 12) * srowpixels + (nextx >> 12)]; if (s32(nexty) >= 0 && nexty < maxy && s32(curx) >= 0 && curx < maxx) pix2 = source[(nexty >> 12) * srowpixels + (curx >> 12)]; if (s32(nexty) >= 0 && nexty < maxy && s32(nextx) >= 0 && nextx < maxx) pix3 = source[(nexty >> 12) * srowpixels + (nextx >> 12)]; // compute the x/y scaling factors curx &= 0xfff; cury &= 0xfff; u32 factor; u32 sumr, sumg, sumb, suma; // contributions from pixel 0 (top,left) factor = (0x1000 - curx) * (0x1000 - cury); sumr = factor * pix0.r(); sumg = factor * pix0.g(); sumb = factor * pix0.b(); suma = factor * pix0.a(); // contributions from pixel 1 (top,right) factor = curx * (0x1000 - cury); sumr += factor * pix1.r(); sumg += factor * pix1.g(); sumb += factor * pix1.b(); suma += factor * pix1.a(); // contributions from pixel 2 (bottom,left) factor = (0x1000 - curx) * cury; sumr += factor * pix2.r(); sumg += factor * pix2.g(); sumb += factor * pix2.b(); suma += factor * pix2.a(); // contributions from pixel 3 (bottom,right) factor = curx * cury; sumr += factor * pix3.r(); sumg += factor * pix3.g(); sumb += factor * pix3.b(); suma += factor * pix3.a(); // apply scaling suma = (suma >> 24) * a / 256; sumr = (sumr >> 24) * r / 256; sumg = (sumg >> 24) * g / 256; sumb = (sumb >> 24) * b / 256; // if we're translucent, add in the destination pixel contribution if (a < 256) { rgb_t const dpix = dest[y * drowpixels + x]; suma += dpix.a() * (256 - a); sumr += dpix.r() * (256 - a); sumg += dpix.g() * (256 - a); sumb += dpix.b() * (256 - a); } // store the target pixel, dividing the RGBA values by the overall scale factor dest[y * drowpixels + x] = rgb_t(suma, sumr, sumg, sumb); } } } //------------------------------------------------- // render_clip_line - clip a line to a rectangle //------------------------------------------------- bool render_clip_line(render_bounds &bounds, const render_bounds &clip) { // loop until we get a final result while (true) { u8 code0 = 0, code1 = 0; u8 thiscode; float x, y; // compute Cohen Sutherland bits for first coordinate if (bounds.y0 > clip.y1) code0 |= 1; if (bounds.y0 < clip.y0) code0 |= 2; if (bounds.x0 > clip.x1) code0 |= 4; if (bounds.x0 < clip.x0) code0 |= 8; // compute Cohen Sutherland bits for second coordinate if (bounds.y1 > clip.y1) code1 |= 1; if (bounds.y1 < clip.y0) code1 |= 2; if (bounds.x1 > clip.x1) code1 |= 4; if (bounds.x1 < clip.x0) code1 |= 8; // trivial accept: just return false if ((code0 | code1) == 0) return false; // trivial reject: just return true if ((code0 & code1) != 0) return true; // fix one of the OOB cases thiscode = code0 ? code0 : code1; // off the bottom if (thiscode & 1) { x = bounds.x0 + (bounds.x1 - bounds.x0) * (clip.y1 - bounds.y0) / (bounds.y1 - bounds.y0); y = clip.y1; } // off the top else if (thiscode & 2) { x = bounds.x0 + (bounds.x1 - bounds.x0) * (clip.y0 - bounds.y0) / (bounds.y1 - bounds.y0); y = clip.y0; } // off the right else if (thiscode & 4) { y = bounds.y0 + (bounds.y1 - bounds.y0) * (clip.x1 - bounds.x0) / (bounds.x1 - bounds.x0); x = clip.x1; } // off the left else { y = bounds.y0 + (bounds.y1 - bounds.y0) * (clip.x0 - bounds.x0) / (bounds.x1 - bounds.x0); x = clip.x0; } // fix the appropriate coordinate if (thiscode == code0) { bounds.x0 = x; bounds.y0 = y; } else { bounds.x1 = x; bounds.y1 = y; } } } //------------------------------------------------- // render_clip_quad - clip a quad to a rectangle //------------------------------------------------- bool render_clip_quad(render_bounds &bounds, const render_bounds &clip, render_quad_texuv *texcoords) { // ensure our assumptions about the bounds are correct assert(bounds.x0 <= bounds.x1); assert(bounds.y0 <= bounds.y1); // trivial reject if (bounds.y1 < clip.y0) return true; if (bounds.y0 > clip.y1) return true; if (bounds.x1 < clip.x0) return true; if (bounds.x0 > clip.x1) return true; // clip top (x0,y0)-(x1,y1) if (bounds.y0 < clip.y0) { float frac = (clip.y0 - bounds.y0) / (bounds.y1 - bounds.y0); bounds.y0 = clip.y0; if (texcoords != nullptr) { texcoords->tl.u += (texcoords->bl.u - texcoords->tl.u) * frac; texcoords->tl.v += (texcoords->bl.v - texcoords->tl.v) * frac; texcoords->tr.u += (texcoords->br.u - texcoords->tr.u) * frac; texcoords->tr.v += (texcoords->br.v - texcoords->tr.v) * frac; } } // clip bottom (x3,y3)-(x2,y2) if (bounds.y1 > clip.y1) { float frac = (bounds.y1 - clip.y1) / (bounds.y1 - bounds.y0); bounds.y1 = clip.y1; if (texcoords != nullptr) { texcoords->bl.u -= (texcoords->bl.u - texcoords->tl.u) * frac; texcoords->bl.v -= (texcoords->bl.v - texcoords->tl.v) * frac; texcoords->br.u -= (texcoords->br.u - texcoords->tr.u) * frac; texcoords->br.v -= (texcoords->br.v - texcoords->tr.v) * frac; } } // clip left (x0,y0)-(x3,y3) if (bounds.x0 < clip.x0) { float frac = (clip.x0 - bounds.x0) / (bounds.x1 - bounds.x0); bounds.x0 = clip.x0; if (texcoords != nullptr) { texcoords->tl.u += (texcoords->tr.u - texcoords->tl.u) * frac; texcoords->tl.v += (texcoords->tr.v - texcoords->tl.v) * frac; texcoords->bl.u += (texcoords->br.u - texcoords->bl.u) * frac; texcoords->bl.v += (texcoords->br.v - texcoords->bl.v) * frac; } } // clip right (x1,y1)-(x2,y2) if (bounds.x1 > clip.x1) { float frac = (bounds.x1 - clip.x1) / (bounds.x1 - bounds.x0); bounds.x1 = clip.x1; if (texcoords != nullptr) { texcoords->tr.u -= (texcoords->tr.u - texcoords->tl.u) * frac; texcoords->tr.v -= (texcoords->tr.v - texcoords->tl.v) * frac; texcoords->br.u -= (texcoords->br.u - texcoords->bl.u) * frac; texcoords->br.v -= (texcoords->br.v - texcoords->bl.v) * frac; } } return false; } //------------------------------------------------- // render_line_to_quad - convert a line and a // width to four points //----------------------------------------------- std::pair render_line_to_quad(const render_bounds &bounds, float width, float length_extension) { render_bounds modbounds = bounds; /* High-level logic -- due to math optimizations, this info is lost below. Imagine a thick line of width (w), drawn from (p0) to (p1), with a unit vector (u) indicating the direction from (p0) to (p1). B C +---------------- ... ------------------+ | ^ | | | | | | | * (p0) ------------> (w)| * (p1) | (u) | | | | | | v | +---------------- ... ------------------+ A D To convert this into a quad, we need to compute the four points A, B, C and D. Starting with point A. We first multiply the unit vector by 0.5w and then rotate the result 90 degrees. Thus, we have: A.x = p0.x + 0.5 * w * u.x * cos(90) - 0.5 * w * u.y * sin(90) A.y = p0.y + 0.5 * w * u.x * sin(90) + 0.5 * w * u.y * cos(90) Conveniently, sin(90) = 1, and cos(90) = 0, so this simplifies to: A.x = p0.x - 0.5 * w * u.y A.y = p0.y + 0.5 * w * u.x Working clockwise around the polygon, the same fallout happens all around as we rotate the unit vector by -90 (B), -90 (C), and 90 (D) degrees: B.x = p0.x + 0.5 * w * u.y B.y = p0.y - 0.5 * w * u.x C.x = p1.x - 0.5 * w * u.y C.y = p1.y + 0.5 * w * u.x D.x = p1.x + 0.5 * w * u.y D.y = p1.y - 0.5 * w * u.x */ // we only care about the half-width float half_width = width * 0.5f; // compute a vector from point 0 to point 1 float unitx = modbounds.x1 - modbounds.x0; float unity = modbounds.y1 - modbounds.y0; // points just use a +1/+1 unit vector; this gives a nice diamond pattern if (unitx == 0 && unity == 0) { // length of a unit vector (1,1) constexpr float unit_length = 0.70710678f; unitx = unity = unit_length * half_width; modbounds.x0 -= unitx; modbounds.y0 -= unity; modbounds.x1 += unitx; modbounds.y1 += unity; } // lines need to be divided by their length else { float length = sqrtf(unitx * unitx + unity * unity); // extend line length if (length_extension > 0.0f) { float half_length_extension = length_extension *0.5f; float directionx = unitx / length; float directiony = unity / length; modbounds.x0 -= directionx * half_length_extension; modbounds.y0 -= directiony * half_length_extension; modbounds.x1 += directionx * half_length_extension; modbounds.y1 += directiony * half_length_extension; } // prescale unitx and unity by the half-width float invlength = half_width / length; unitx *= invlength; unity *= invlength; } // rotate the unit vector by 90 and -90 degrees and add to points 0 and 1 return std::make_pair( render_bounds{ modbounds.x0 - unity, modbounds.y0 + unitx, modbounds.x0 + unity, modbounds.y0 - unitx }, render_bounds{ modbounds.x1 - unity, modbounds.y1 + unitx, modbounds.x1 + unity, modbounds.y1 - unitx }); } /*------------------------------------------------- render_load_msdib - load a Microsoft DIB file into a bitmap -------------------------------------------------*/ void render_load_msdib(bitmap_argb32 &bitmap, util::random_read &file) noexcept { // deallocate previous bitmap bitmap.reset(); // read the DIB data util::msdib_error const result = util::msdib_read_bitmap(file, bitmap); if (result != util::msdib_error::NONE) { osd_printf_error("Error reading Microsoft DIB file\n"); bitmap.reset(); } } /*------------------------------------------------- render_load_jpeg - load a JPEG file into a bitmap -------------------------------------------------*/ void render_load_jpeg(bitmap_argb32 &bitmap, util::random_read &file) noexcept { // deallocate previous bitmap bitmap.reset(); // set up context for error handling jpeg_decompress_struct cinfo; jpeg_setjmp_error_mgr jerr; cinfo.err = &jerr; JSAMPARRAY buffer = nullptr; int w, h, s, row_stride, j, i; if (setjmp(jerr.m_jump_buffer)) // setjmp must be used in control expression { char msg[1024]; (cinfo.err->format_message)(reinterpret_cast(&cinfo), msg); osd_printf_error("JPEG error reading data from file: %s\n", msg); bitmap.reset(); goto cleanup; // use goto to ensure longjmp can't cross an initialisation } // create a JPEG source for the file jpeg_create_decompress(&cinfo); cinfo.mem->max_memory_to_use = 128 * 1024 * 1024; jpeg_corefile_source::source(&cinfo, file); // read JPEG header and start decompression jpeg_read_header(&cinfo, TRUE); jpeg_start_decompress(&cinfo); // allocates the destination bitmap w = cinfo.output_width; h = cinfo.output_height; s = cinfo.output_components; bitmap.allocate(w, h); // allocates a buffer to receive the information and copy them into the bitmap row_stride = cinfo.output_width * cinfo.output_components; buffer = reinterpret_cast(std::malloc(sizeof(JSAMPROW))); if (buffer) buffer[0] = reinterpret_cast(std::malloc(sizeof(JSAMPLE) * row_stride)); if (bitmap.valid() && buffer && buffer[0]) { while (cinfo.output_scanline < cinfo.output_height) { j = cinfo.output_scanline; jpeg_read_scanlines(&cinfo, buffer, 1); if (s == 1) { for (i = 0; i < w; ++i) bitmap.pix(j, i) = rgb_t(0xff, buffer[0][i], buffer[0][i], buffer[0][i]); } else if (s == 3) { for (i = 0; i < w; ++i) bitmap.pix(j, i) = rgb_t(0xff, buffer[0][i * s], buffer[0][i * s + 1], buffer[0][i * s + 2]); } else { osd_printf_error("Cannot read JPEG data from file.\n"); bitmap.reset(); break; } } } else { osd_printf_error("Error allocating memory for JPEG image.\n"); bitmap.reset(); } // finish decompression and free the memory jpeg_finish_decompress(&cinfo); cleanup: jpeg_destroy_decompress(&cinfo); if (buffer) { if (buffer[0]) std::free(buffer[0]); std::free(buffer); } } /*------------------------------------------------- render_load_png - load a PNG file into a bitmap -------------------------------------------------*/ bool render_load_png(bitmap_argb32 &bitmap, util::random_read &file, bool load_as_alpha_to_existing) noexcept { // deallocate if we're not overlaying alpha if (!load_as_alpha_to_existing) bitmap.reset(); // read the PNG data util::png_info png; std::error_condition const result = png.read_file(file); if (result) { osd_printf_error("Error reading PNG file\n"); return false; } // if less than 8 bits, upsample if (png.expand_buffer_8bit()) { osd_printf_error("Error upsampling PNG bitmap\n"); return false; } bool hasalpha = false; if (!load_as_alpha_to_existing) { // non-alpha case if (png.copy_to_bitmap(bitmap, hasalpha)) { osd_printf_error("Error copying PNG bitmap to MAME bitmap\n"); return false; } } else if (png.width == bitmap.width() && png.height == bitmap.height()) { // verify we can handle this PNG if (png.bit_depth > 8) { osd_printf_error("Unsupported bit depth %d (8 bit max)\n", png.bit_depth); return false; } // alpha case hasalpha = copy_png_alpha_to_bitmap(bitmap, png); } // free PNG data return hasalpha; } /*------------------------------------------------- copy_png_alpha_to_bitmap - copy the PNG data to the alpha channel of a bitmap -------------------------------------------------*/ static bool copy_png_alpha_to_bitmap(bitmap_argb32 &bitmap, const util::png_info &png) noexcept { // FIXME: this function is basically copy/pasted from the PNG code in util, and should be unified with it u8 accumalpha = 0xff; // colour format table static constexpr unsigned samples[] = { 1, 0, 3, 1, 2, 0, 4 }; // adam7 interlace tables static constexpr unsigned x_bias[7] = { 7, 3, 3, 1, 1, 0, 0 }; static constexpr unsigned y_bias[7] = { 7, 7, 3, 3, 1, 1, 0 }; static constexpr unsigned x_shift[7] = { 3, 3, 2, 2, 1, 1, 0 }; static constexpr unsigned y_shift[7] = { 3, 3, 3, 2, 2, 1, 1 }; unsigned const pass_count(png.interlace_method ? 7 : 1); u32 pass_offset[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; for (unsigned pass = 0; pass_count > pass; ++pass) { // calculate offset for next interlace pass u32 const width(png.interlace_method ? ((png.width + x_bias[pass]) >> x_shift[pass]) : png.width); u32 const height(png.interlace_method ? ((png.height + y_bias[pass]) >> y_shift[pass]) : png.height); u32 const rowbytes(((width * samples[png.color_type] * png.bit_depth) + 7) >> 3); pass_offset[pass + 1] = pass_offset[pass] + (height * (rowbytes + 1)); u8 const *src(&png.image[pass_offset[pass]]); auto const x_trans = [offs = (1 << x_shift[pass]) - x_bias[pass] - 1, shift = x_shift[pass]] (u32 x) { return (x << shift) + offs; }; auto const y_trans = [offs = (1 << y_shift[pass]) - y_bias[pass] - 1, shift = y_shift[pass]] (u32 y) { return (y << shift) + offs; }; if (png.color_type == 3) { // handle 8bpp palettized case for (u32 y = 0; height > y; ++y) { for (u32 x = 0; width > x; ++x, ++src) { bitmap_argb32::pixel_t &dest(png.interlace_method ? bitmap.pix(y_trans(y), x_trans(x)) : bitmap.pix(y, x)); rgb_t const pixel(dest); u8 const alpha(rgb_t(png.palette[*src * 3], png.palette[*src * 3 + 1], png.palette[*src * 3 + 2]).brightness()); accumalpha &= alpha; dest = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b()); } } } else if (png.color_type == 0) { // handle 8bpp grayscale non-alpha case for (u32 y = 0; height > y; ++y) { for (u32 x = 0; width > x; ++x, ++src) { bitmap_argb32::pixel_t &dest(png.interlace_method ? bitmap.pix(y_trans(y), x_trans(x)) : bitmap.pix(y, x)); rgb_t const pixel(dest); accumalpha &= *src; dest = rgb_t(*src, pixel.r(), pixel.g(), pixel.b()); } } } else if (png.color_type == 4) { // handle 8bpp grayscale alpha case for (u32 y = 0; height > y; ++y) { for (u32 x = 0; width > x; ++x, src += 2) { bitmap_argb32::pixel_t &dest(png.interlace_method ? bitmap.pix(y_trans(y), x_trans(x)) : bitmap.pix(y, x)); rgb_t const pixel(dest); accumalpha &= *src; dest = rgb_t(*src, pixel.r(), pixel.g(), pixel.b()); } } } else if (png.color_type == 2) { // handle 32bpp non-alpha case for (u32 y = 0; height > y; ++y) { for (u32 x = 0; width > x; ++x, src += 3) { bitmap_argb32::pixel_t &dest(png.interlace_method ? bitmap.pix(y_trans(y), x_trans(x)) : bitmap.pix(y, x)); rgb_t const pixel(dest); u8 const alpha(rgb_t(src[0], src[1], src[2]).brightness()); accumalpha &= alpha; dest = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b()); } } } else { // handle 32bpp alpha case for (u32 y = 0; height > y; ++y) { for (u32 x = 0; width > x; ++x, src += 4) { bitmap_argb32::pixel_t &dest(png.interlace_method ? bitmap.pix(y_trans(y), x_trans(x)) : bitmap.pix(y, x)); rgb_t const pixel(dest); u8 const alpha(rgb_t(src[0], src[1], src[2]).brightness()); accumalpha &= alpha; dest = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b()); } } } } // set the hasalpha flag return (accumalpha != 0xff); } /*------------------------------------------------- render_detect_image - detect image format -------------------------------------------------*/ ru_imgformat render_detect_image(util::random_read &file) noexcept { // PNG: check for valid header { std::error_condition const png = util::png_info::verify_header(file); file.seek(0, SEEK_SET); // TODO: check error return if (!png) return RENDUTIL_IMGFORMAT_PNG; } // JPEG: attempt to read header with libjpeg { jpeg_decompress_struct cinfo; jpeg_setjmp_error_mgr jerr; cinfo.err = &jerr; if (setjmp(jerr.m_jump_buffer)) // setjmp must be used in control expression goto notjpeg; // use goto to ensure longjmp can't cross an initialisation jpeg_create_decompress(&cinfo); cinfo.mem->max_memory_to_use = 128 * 1024 * 1024; jpeg_corefile_source::source(&cinfo, file); jpeg_read_header(&cinfo, TRUE); jpeg_destroy_decompress(&cinfo); file.seek(0, SEEK_SET); // TODO: check error return return RENDUTIL_IMGFORMAT_JPEG; notjpeg: jpeg_destroy_decompress(&cinfo); file.seek(0, SEEK_SET); } // Microsoft DIB: check for valid header { util::msdib_error const msdib = util::msdib_verify_header(file); file.seek(0, SEEK_SET); if (util::msdib_error::NONE == msdib) return RENDUTIL_IMGFORMAT_MSDIB; } // TODO: add more as necessary return RENDUTIL_IMGFORMAT_UNKNOWN; }