| /* | |
| * Copyright © 2026 Behdad Esfahbod | |
| * | |
| * This is part of HarfBuzz, a text shaping library. | |
| * | |
| * Permission is hereby granted, without written agreement and without | |
| * license or royalty fees, to use, copy, modify, and distribute this | |
| * software and its documentation for any purpose, provided that the | |
| * above copyright notice and the following two paragraphs appear in | |
| * all copies of this software. | |
| * | |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR | |
| * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES | |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN | |
| * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH | |
| * DAMAGE. | |
| * | |
| * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, | |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
| * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS | |
| * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO | |
| * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
| * | |
| * Author(s): Behdad Esfahbod | |
| */ | |
| #include "hb.hh" | |
| #include "hb-raster-image.hh" | |
| #include <math.h> | |
| /** | |
| * SECTION:hb-raster | |
| * @title: hb-raster | |
| * @short_description: Glyph rasterization | |
| * @include: hb-raster.h | |
| * | |
| * Functions for rasterizing glyph outlines into pixel buffers. | |
| * | |
| * #hb_raster_draw_t rasterizes outline geometry and always outputs | |
| * @HB_RASTER_FORMAT_A8. Typical flow: | |
| * | |
| * |[<!-- language="plain" --> | |
| * hb_raster_draw_t *draw = hb_raster_draw_create_or_fail (); | |
| * hb_raster_draw_set_scale_factor (draw, 64.f, 64.f); | |
| * hb_raster_draw_set_transform (draw, 1.f, 0.f, 0.f, 1.f, 0.f, 0.f); | |
| * hb_glyph_extents_t glyph_extents; | |
| * hb_font_get_glyph_extents (font, gid, &glyph_extents); | |
| * hb_raster_draw_set_glyph_extents (draw, &glyph_extents); | |
| * hb_raster_draw_glyph (draw, font, gid); | |
| * hb_raster_image_t *mask = hb_raster_draw_render (draw); | |
| * hb_raster_extents_t image_extents; | |
| * hb_raster_image_get_extents (mask, &image_extents); | |
| * ]| | |
| * | |
| * #hb_raster_paint_t renders color paint graphs and always outputs | |
| * @HB_RASTER_FORMAT_BGRA32. Typical flow: | |
| * | |
| * |[<!-- language="plain" --> | |
| * hb_raster_paint_t *paint = hb_raster_paint_create_or_fail (); | |
| * hb_raster_paint_set_scale_factor (paint, 64.f, 64.f); | |
| * hb_raster_paint_set_transform (paint, 1.f, 0.f, 0.f, 1.f, 0.f, 0.f); | |
| * hb_raster_paint_set_foreground (paint, foreground); | |
| * hb_glyph_extents_t glyph_extents; | |
| * hb_font_get_glyph_extents (font, gid, &glyph_extents); | |
| * hb_raster_paint_set_glyph_extents (paint, &glyph_extents); | |
| * hb_raster_paint_glyph (paint, font, gid); | |
| * hb_raster_image_t *img = hb_raster_paint_render (paint); | |
| * hb_raster_extents_t image_extents; | |
| * hb_raster_image_get_extents (img, &image_extents); | |
| * ]| | |
| * | |
| * In both modes, set extents explicitly (or via glyph extents) before | |
| * rendering to avoid implicit allocations and to get deterministic bounds. | |
| * When caching individual glyph images for later composition, position each | |
| * image using the `x_origin` and `y_origin` members returned by | |
| * hb_raster_image_get_extents(). These are the pixel-space left and bottom | |
| * edges after raster-bound rounding; do not reconstruct them from glyph | |
| * bearings and image dimensions. | |
| **/ | |
| #ifdef HAVE_PNG | |
| #include <png.h> | |
| #endif | |
| #ifdef HAVE_PNG | |
| struct hb_raster_png_read_blob_t | |
| { | |
| const uint8_t *data = nullptr; | |
| size_t size = 0; | |
| size_t offset = 0; | |
| uint8_t *rgba = nullptr; | |
| png_bytep *rows = nullptr; | |
| }; | |
| static void | |
| hb_raster_png_error (png_structp png, | |
| png_const_charp msg HB_UNUSED) | |
| { | |
| #ifdef PNG_SETJMP_SUPPORTED | |
| longjmp (png_jmpbuf (png), 1); | |
| #endif | |
| } | |
| static void | |
| hb_raster_png_warning (png_structp png HB_UNUSED, | |
| png_const_charp msg HB_UNUSED) | |
| {} | |
| static void | |
| hb_raster_png_read_blob (png_structp png, png_bytep out, png_size_t length) | |
| { | |
| hb_raster_png_read_blob_t *r = (hb_raster_png_read_blob_t *) png_get_io_ptr (png); | |
| if (!r || !r->data || length > r->size - r->offset) | |
| png_error (png, "read error"); | |
| hb_memcpy (out, r->data + r->offset, length); | |
| r->offset += length; | |
| } | |
| static void | |
| hb_raster_png_read_blob_fini (hb_raster_png_read_blob_t *r) | |
| { | |
| if (!r) | |
| return; | |
| hb_free (r->rgba); | |
| hb_free (r->rows); | |
| hb_free (r); | |
| } | |
| struct hb_raster_png_write_blob_t | |
| { | |
| char *data = nullptr; | |
| size_t length = 0; | |
| size_t allocated = 0; | |
| uint8_t *rgba = nullptr; | |
| png_bytep *rows = nullptr; | |
| }; | |
| static void | |
| hb_raster_png_write_blob (png_structp png, png_bytep in, png_size_t length) | |
| { | |
| hb_raster_png_write_blob_t *w = (hb_raster_png_write_blob_t *) png_get_io_ptr (png); | |
| if (!w) | |
| png_error (png, "write error"); | |
| size_t old_length = w->length; | |
| if ((size_t) length > (size_t) -1 - old_length) | |
| png_error (png, "write error"); | |
| size_t new_length = old_length + length; | |
| if (new_length > w->allocated) | |
| { | |
| size_t new_allocated = w->allocated ? w->allocated : 4096; | |
| while (new_allocated < new_length) | |
| { | |
| size_t next = new_allocated * 2; | |
| if (next <= new_allocated) | |
| { | |
| new_allocated = new_length; | |
| break; | |
| } | |
| new_allocated = next; | |
| } | |
| char *data = (char *) hb_realloc (w->data, new_allocated); | |
| if (!data) | |
| png_error (png, "write error"); | |
| w->data = data; | |
| w->allocated = new_allocated; | |
| } | |
| hb_memcpy (w->data + old_length, in, length); | |
| w->length = new_length; | |
| } | |
| static void | |
| hb_raster_png_flush_blob (png_structp png HB_UNUSED) | |
| {} | |
| static void | |
| hb_raster_png_write_blob_fini (hb_raster_png_write_blob_t *w) | |
| { | |
| if (!w) | |
| return; | |
| hb_free (w->data); | |
| hb_free (w->rgba); | |
| hb_free (w->rows); | |
| hb_free (w); | |
| } | |
| #endif | |
| /* | |
| * Image compositing | |
| */ | |
| /* Unpack premultiplied pixel to float RGBA [0,1]. */ | |
| static inline void | |
| unpack_to_float (uint32_t px, float &r, float &g, float &b, float &a) | |
| { | |
| b = (px & 0xFF) / 255.f; | |
| g = ((px >> 8) & 0xFF) / 255.f; | |
| r = ((px >> 16) & 0xFF) / 255.f; | |
| a = (px >> 24) / 255.f; | |
| } | |
| /* Pack float RGBA [0,1] premultiplied back to uint32_t. */ | |
| static inline uint32_t | |
| pack_from_float (float r, float g, float b, float a) | |
| { | |
| return hb_raster_pack_pixel ((uint8_t) (hb_clamp (b, 0.f, 1.f) * 255.f + 0.5f), | |
| (uint8_t) (hb_clamp (g, 0.f, 1.f) * 255.f + 0.5f), | |
| (uint8_t) (hb_clamp (r, 0.f, 1.f) * 255.f + 0.5f), | |
| (uint8_t) (hb_clamp (a, 0.f, 1.f) * 255.f + 0.5f)); | |
| } | |
| /* Separable blend mode functions: operate on unpremultiplied [0,1] channels. */ | |
| static inline float | |
| blend_multiply (float sc, float dc) { return sc * dc; } | |
| static inline float | |
| blend_screen (float sc, float dc) { return sc + dc - sc * dc; } | |
| static inline float | |
| blend_overlay (float sc, float dc) | |
| { return dc <= 0.5f ? 2.f * sc * dc : 1.f - 2.f * (1.f - sc) * (1.f - dc); } | |
| static inline float | |
| blend_darken (float sc, float dc) { return hb_min (sc, dc); } | |
| static inline float | |
| blend_lighten (float sc, float dc) { return hb_max (sc, dc); } | |
| static inline float | |
| blend_color_dodge (float sc, float dc) | |
| { | |
| if (dc <= 0.f) return 0.f; | |
| if (sc >= 1.f) return 1.f; | |
| return hb_min (1.f, dc / (1.f - sc)); | |
| } | |
| static inline float | |
| blend_color_burn (float sc, float dc) | |
| { | |
| if (dc >= 1.f) return 1.f; | |
| if (sc <= 0.f) return 0.f; | |
| return 1.f - hb_min (1.f, (1.f - dc) / sc); | |
| } | |
| static inline float | |
| blend_hard_light (float sc, float dc) | |
| { return sc <= 0.5f ? 2.f * sc * dc : 1.f - 2.f * (1.f - sc) * (1.f - dc); } | |
| static inline float | |
| blend_soft_light (float sc, float dc) | |
| { | |
| if (sc <= 0.5f) | |
| return dc - (1.f - 2.f * sc) * dc * (1.f - dc); | |
| float d = (dc <= 0.25f) ? ((16.f * dc - 12.f) * dc + 4.f) * dc | |
| : sqrtf (dc); | |
| return dc + (2.f * sc - 1.f) * (d - dc); | |
| } | |
| static inline float | |
| blend_difference (float sc, float dc) { return fabsf (sc - dc); } | |
| static inline float | |
| blend_exclusion (float sc, float dc) { return sc + dc - 2.f * sc * dc; } | |
| /* Apply a separable blend mode per-pixel. | |
| * Both src and dst are premultiplied BGRA32. */ | |
| static inline uint32_t | |
| apply_separable_blend (uint32_t src, uint32_t dst, | |
| float (*blend_fn)(float, float)) | |
| { | |
| float sr, sg, sb, sa; | |
| float dr, dg, db, da; | |
| unpack_to_float (src, sr, sg, sb, sa); | |
| unpack_to_float (dst, dr, dg, db, da); | |
| float usr = sa > 0.f ? sr / sa : 0.f; | |
| float usg = sa > 0.f ? sg / sa : 0.f; | |
| float usb = sa > 0.f ? sb / sa : 0.f; | |
| float udr = da > 0.f ? dr / da : 0.f; | |
| float udg = da > 0.f ? dg / da : 0.f; | |
| float udb = da > 0.f ? db / da : 0.f; | |
| float br = blend_fn (usr, udr); | |
| float bg = blend_fn (usg, udg); | |
| float bb = blend_fn (usb, udb); | |
| float ra = sa + da - sa * da; | |
| float rr = sa * da * br + sa * (1.f - da) * usr + (1.f - sa) * da * udr; | |
| float rg = sa * da * bg + sa * (1.f - da) * usg + (1.f - sa) * da * udg; | |
| float rb = sa * da * bb + sa * (1.f - da) * usb + (1.f - sa) * da * udb; | |
| return pack_from_float (rr, rg, rb, ra); | |
| } | |
| /* HSL helpers */ | |
| static inline float | |
| hsl_luminosity (float r, float g, float b) | |
| { return 0.299f * r + 0.587f * g + 0.114f * b; } | |
| static inline float | |
| hsl_saturation (float r, float g, float b) | |
| { return hb_max (hb_max (r, g), b) - hb_min (hb_min (r, g), b); } | |
| static inline void | |
| hsl_clip_color (float &r, float &g, float &b) | |
| { | |
| float l = hsl_luminosity (r, g, b); | |
| float mn = hb_min (hb_min (r, g), b); | |
| float mx = hb_max (hb_max (r, g), b); | |
| if (mn < 0.f) | |
| { | |
| float d = l - mn; | |
| if (d > 0.f) { r = l + (r - l) * l / d; g = l + (g - l) * l / d; b = l + (b - l) * l / d; } | |
| } | |
| if (mx > 1.f) | |
| { | |
| float d = mx - l; | |
| if (d > 0.f) { r = l + (r - l) * (1.f - l) / d; g = l + (g - l) * (1.f - l) / d; b = l + (b - l) * (1.f - l) / d; } | |
| } | |
| } | |
| static inline void | |
| hsl_set_luminosity (float &r, float &g, float &b, float l) | |
| { | |
| float d = l - hsl_luminosity (r, g, b); | |
| r += d; g += d; b += d; | |
| hsl_clip_color (r, g, b); | |
| } | |
| static inline void | |
| hsl_set_saturation_inner (float &mn, float &mid, float &mx, float s) | |
| { | |
| if (mx > mn) | |
| { | |
| mid = (mid - mn) * s / (mx - mn); | |
| mx = s; | |
| } | |
| else | |
| mid = mx = 0.f; | |
| mn = 0.f; | |
| } | |
| static inline void | |
| hsl_set_saturation (float &r, float &g, float &b, float s) | |
| { | |
| if (r <= g) | |
| { | |
| if (g <= b) hsl_set_saturation_inner (r, g, b, s); | |
| else if (r <= b) hsl_set_saturation_inner (r, b, g, s); | |
| else hsl_set_saturation_inner (b, r, g, s); | |
| } | |
| else | |
| { | |
| if (r <= b) hsl_set_saturation_inner (g, r, b, s); | |
| else if (g <= b) hsl_set_saturation_inner (g, b, r, s); | |
| else hsl_set_saturation_inner (b, g, r, s); | |
| } | |
| } | |
| static inline uint32_t | |
| apply_hsl_blend (uint32_t src, uint32_t dst, | |
| hb_paint_composite_mode_t mode) | |
| { | |
| float sr, sg, sb, sa; | |
| float dr, dg, db, da; | |
| unpack_to_float (src, sr, sg, sb, sa); | |
| unpack_to_float (dst, dr, dg, db, da); | |
| float usr = sa > 0.f ? sr / sa : 0.f; | |
| float usg = sa > 0.f ? sg / sa : 0.f; | |
| float usb = sa > 0.f ? sb / sa : 0.f; | |
| float udr = da > 0.f ? dr / da : 0.f; | |
| float udg = da > 0.f ? dg / da : 0.f; | |
| float udb = da > 0.f ? db / da : 0.f; | |
| float br = udr, bg = udg, bb = udb; | |
| if (mode == HB_PAINT_COMPOSITE_MODE_HSL_HUE) | |
| { | |
| br = usr; bg = usg; bb = usb; | |
| hsl_set_saturation (br, bg, bb, hsl_saturation (udr, udg, udb)); | |
| hsl_set_luminosity (br, bg, bb, hsl_luminosity (udr, udg, udb)); | |
| } | |
| else if (mode == HB_PAINT_COMPOSITE_MODE_HSL_SATURATION) | |
| { | |
| br = udr; bg = udg; bb = udb; | |
| hsl_set_saturation (br, bg, bb, hsl_saturation (usr, usg, usb)); | |
| hsl_set_luminosity (br, bg, bb, hsl_luminosity (udr, udg, udb)); | |
| } | |
| else if (mode == HB_PAINT_COMPOSITE_MODE_HSL_COLOR) | |
| { | |
| br = usr; bg = usg; bb = usb; | |
| hsl_set_luminosity (br, bg, bb, hsl_luminosity (udr, udg, udb)); | |
| } | |
| else /* HSL_LUMINOSITY */ | |
| { | |
| br = udr; bg = udg; bb = udb; | |
| hsl_set_luminosity (br, bg, bb, hsl_luminosity (usr, usg, usb)); | |
| } | |
| float ra = sa + da - sa * da; | |
| float rr = sa * da * br + sa * (1.f - da) * usr + (1.f - sa) * da * udr; | |
| float rg = sa * da * bg + sa * (1.f - da) * usg + (1.f - sa) * da * udg; | |
| float rb = sa * da * bb + sa * (1.f - da) * usb + (1.f - sa) * da * udb; | |
| return pack_from_float (rr, rg, rb, ra); | |
| } | |
| /* Composite per-pixel with full blend mode support. */ | |
| static inline uint32_t | |
| composite_pixel (uint32_t src, uint32_t dst, | |
| hb_paint_composite_mode_t mode) | |
| { | |
| uint8_t sa = (uint8_t) (src >> 24); | |
| uint8_t da = (uint8_t) (dst >> 24); | |
| switch (mode) | |
| { | |
| case HB_PAINT_COMPOSITE_MODE_CLEAR: | |
| return 0; | |
| case HB_PAINT_COMPOSITE_MODE_SRC: | |
| return src; | |
| case HB_PAINT_COMPOSITE_MODE_DEST: | |
| return dst; | |
| case HB_PAINT_COMPOSITE_MODE_SRC_OVER: | |
| return hb_raster_src_over (src, dst); | |
| case HB_PAINT_COMPOSITE_MODE_DEST_OVER: | |
| return hb_raster_src_over (dst, src); | |
| case HB_PAINT_COMPOSITE_MODE_SRC_IN: | |
| return hb_raster_alpha_mul (src, da); | |
| case HB_PAINT_COMPOSITE_MODE_DEST_IN: | |
| return hb_raster_alpha_mul (dst, sa); | |
| case HB_PAINT_COMPOSITE_MODE_SRC_OUT: | |
| return hb_raster_alpha_mul (src, 255 - da); | |
| case HB_PAINT_COMPOSITE_MODE_DEST_OUT: | |
| return hb_raster_alpha_mul (dst, 255 - sa); | |
| case HB_PAINT_COMPOSITE_MODE_SRC_ATOP: | |
| { | |
| /* Fa=Da, Fb=1-Sa */ | |
| uint32_t a = hb_raster_alpha_mul (src, da); | |
| uint32_t b = hb_raster_alpha_mul (dst, 255 - sa); | |
| uint8_t rb = (uint8_t) hb_min (255u, (unsigned) (a & 0xFF) + (b & 0xFF)); | |
| uint8_t rg = (uint8_t) hb_min (255u, (unsigned) ((a >> 8) & 0xFF) + ((b >> 8) & 0xFF)); | |
| uint8_t rr = (uint8_t) hb_min (255u, (unsigned) ((a >> 16) & 0xFF) + ((b >> 16) & 0xFF)); | |
| uint8_t ra = (uint8_t) hb_min (255u, (unsigned) (a >> 24) + (b >> 24)); | |
| return hb_raster_pack_pixel (rb, rg, rr, ra); | |
| } | |
| case HB_PAINT_COMPOSITE_MODE_DEST_ATOP: | |
| { | |
| uint32_t a = hb_raster_alpha_mul (dst, sa); | |
| uint32_t b = hb_raster_alpha_mul (src, 255 - da); | |
| uint8_t rb = (uint8_t) hb_min (255u, (unsigned) (a & 0xFF) + (b & 0xFF)); | |
| uint8_t rg = (uint8_t) hb_min (255u, (unsigned) ((a >> 8) & 0xFF) + ((b >> 8) & 0xFF)); | |
| uint8_t rr = (uint8_t) hb_min (255u, (unsigned) ((a >> 16) & 0xFF) + ((b >> 16) & 0xFF)); | |
| uint8_t ra = (uint8_t) hb_min (255u, (unsigned) (a >> 24) + (b >> 24)); | |
| return hb_raster_pack_pixel (rb, rg, rr, ra); | |
| } | |
| case HB_PAINT_COMPOSITE_MODE_XOR: | |
| { | |
| uint32_t a = hb_raster_alpha_mul (src, 255 - da); | |
| uint32_t b = hb_raster_alpha_mul (dst, 255 - sa); | |
| uint8_t rb = (uint8_t) hb_min (255u, (unsigned) (a & 0xFF) + (b & 0xFF)); | |
| uint8_t rg = (uint8_t) hb_min (255u, (unsigned) ((a >> 8) & 0xFF) + ((b >> 8) & 0xFF)); | |
| uint8_t rr = (uint8_t) hb_min (255u, (unsigned) ((a >> 16) & 0xFF) + ((b >> 16) & 0xFF)); | |
| uint8_t ra = (uint8_t) hb_min (255u, (unsigned) (a >> 24) + (b >> 24)); | |
| return hb_raster_pack_pixel (rb, rg, rr, ra); | |
| } | |
| case HB_PAINT_COMPOSITE_MODE_PLUS: | |
| { | |
| uint8_t rb = (uint8_t) hb_min (255u, (unsigned) (src & 0xFF) + (dst & 0xFF)); | |
| uint8_t rg = (uint8_t) hb_min (255u, (unsigned) ((src >> 8) & 0xFF) + ((dst >> 8) & 0xFF)); | |
| uint8_t rr = (uint8_t) hb_min (255u, (unsigned) ((src >> 16) & 0xFF) + ((dst >> 16) & 0xFF)); | |
| uint8_t ra = (uint8_t) hb_min (255u, (unsigned) (src >> 24) + (dst >> 24)); | |
| return hb_raster_pack_pixel (rb, rg, rr, ra); | |
| } | |
| case HB_PAINT_COMPOSITE_MODE_MULTIPLY: return apply_separable_blend (src, dst, blend_multiply); | |
| case HB_PAINT_COMPOSITE_MODE_SCREEN: return apply_separable_blend (src, dst, blend_screen); | |
| case HB_PAINT_COMPOSITE_MODE_OVERLAY: return apply_separable_blend (src, dst, blend_overlay); | |
| case HB_PAINT_COMPOSITE_MODE_DARKEN: return apply_separable_blend (src, dst, blend_darken); | |
| case HB_PAINT_COMPOSITE_MODE_LIGHTEN: return apply_separable_blend (src, dst, blend_lighten); | |
| case HB_PAINT_COMPOSITE_MODE_COLOR_DODGE: return apply_separable_blend (src, dst, blend_color_dodge); | |
| case HB_PAINT_COMPOSITE_MODE_COLOR_BURN: return apply_separable_blend (src, dst, blend_color_burn); | |
| case HB_PAINT_COMPOSITE_MODE_HARD_LIGHT: return apply_separable_blend (src, dst, blend_hard_light); | |
| case HB_PAINT_COMPOSITE_MODE_SOFT_LIGHT: return apply_separable_blend (src, dst, blend_soft_light); | |
| case HB_PAINT_COMPOSITE_MODE_DIFFERENCE: return apply_separable_blend (src, dst, blend_difference); | |
| case HB_PAINT_COMPOSITE_MODE_EXCLUSION: return apply_separable_blend (src, dst, blend_exclusion); | |
| case HB_PAINT_COMPOSITE_MODE_HSL_HUE: | |
| case HB_PAINT_COMPOSITE_MODE_HSL_SATURATION: | |
| case HB_PAINT_COMPOSITE_MODE_HSL_COLOR: | |
| case HB_PAINT_COMPOSITE_MODE_HSL_LUMINOSITY: | |
| return apply_hsl_blend (src, dst, mode); | |
| default: | |
| return hb_raster_src_over (src, dst); | |
| } | |
| } | |
| /* hb_raster_image_t */ | |
| unsigned | |
| hb_raster_image_t::bytes_per_pixel (hb_raster_format_t format) | |
| { | |
| return format == HB_RASTER_FORMAT_BGRA32 ? 4u : 1u; | |
| } | |
| bool | |
| hb_raster_image_t::configure (hb_raster_format_t format, | |
| hb_raster_extents_t extents) | |
| { | |
| if (format != HB_RASTER_FORMAT_A8 && | |
| format != HB_RASTER_FORMAT_BGRA32) | |
| format = HB_RASTER_FORMAT_A8; | |
| unsigned bpp = bytes_per_pixel (format); | |
| if (extents.width > UINT_MAX / bpp) | |
| return false; | |
| unsigned min_stride = extents.width * bpp; | |
| if (extents.stride == 0 || extents.stride < min_stride) | |
| extents.stride = min_stride; | |
| if (extents.height && extents.stride > (size_t) -1 / extents.height) | |
| return false; | |
| size_t buf_size = (size_t) extents.stride * extents.height; | |
| if (buf_size > HB_RASTER_MAX_BUFFER_SIZE) | |
| return false; | |
| if (unlikely (!buffer.resize_dirty (buf_size))) | |
| return false; | |
| this->format = format; | |
| this->extents = extents; | |
| return true; | |
| } | |
| bool | |
| hb_raster_image_t::deserialize_from_png (hb_blob_t *blob) | |
| { | |
| #ifndef HAVE_PNG | |
| return false; | |
| #else | |
| if (!blob) | |
| return false; | |
| unsigned blob_len = 0; | |
| const uint8_t *blob_data = (const uint8_t *) hb_blob_get_data (blob, &blob_len); | |
| if (!blob_data || !blob_len) | |
| return false; | |
| png_structp png = png_create_read_struct (PNG_LIBPNG_VER_STRING, nullptr, | |
| hb_raster_png_error, | |
| hb_raster_png_warning); | |
| if (!png) | |
| return false; | |
| png_infop info = png_create_info_struct (png); | |
| if (!info) | |
| { | |
| png_destroy_read_struct (&png, nullptr, nullptr); | |
| return false; | |
| } | |
| hb_raster_png_read_blob_t *reader = (hb_raster_png_read_blob_t *) hb_calloc (1, sizeof (*reader)); | |
| if (!reader) | |
| { | |
| png_destroy_read_struct (&png, &info, nullptr); | |
| return false; | |
| } | |
| reader->data = blob_data; | |
| reader->size = (size_t) blob_len; | |
| png_set_read_fn (png, reader, hb_raster_png_read_blob); | |
| if (setjmp (png_jmpbuf (png))) | |
| { | |
| png_destroy_read_struct (&png, &info, nullptr); | |
| hb_raster_png_read_blob_fini (reader); | |
| return false; | |
| } | |
| png_read_info (png, info); | |
| png_uint_32 w = 0, h = 0; | |
| int bit_depth = 0, color_type = 0; | |
| int interlace_type = 0, compression_type = 0, filter_method = 0; | |
| png_get_IHDR (png, info, &w, &h, &bit_depth, &color_type, | |
| &interlace_type, &compression_type, &filter_method); | |
| if (!w || !h || w > (png_uint_32) INT_MAX || h > (png_uint_32) INT_MAX) | |
| { | |
| png_destroy_read_struct (&png, &info, nullptr); | |
| hb_raster_png_read_blob_fini (reader); | |
| return false; | |
| } | |
| bool has_trns = png_get_valid (png, info, PNG_INFO_tRNS); | |
| if (bit_depth == 16) | |
| png_set_strip_16 (png); | |
| if (color_type == PNG_COLOR_TYPE_PALETTE) | |
| png_set_palette_to_rgb (png); | |
| if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) | |
| png_set_expand_gray_1_2_4_to_8 (png); | |
| if (has_trns) | |
| png_set_tRNS_to_alpha (png); | |
| if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) | |
| png_set_gray_to_rgb (png); | |
| if (!(color_type & PNG_COLOR_MASK_ALPHA) && !has_trns) | |
| png_set_add_alpha (png, 0xff, PNG_FILLER_AFTER); | |
| if (interlace_type != PNG_INTERLACE_NONE) | |
| png_set_interlace_handling (png); | |
| png_read_update_info (png, info); | |
| if (png_get_bit_depth (png, info) != 8 || png_get_channels (png, info) != 4) | |
| { | |
| png_destroy_read_struct (&png, &info, nullptr); | |
| hb_raster_png_read_blob_fini (reader); | |
| return false; | |
| } | |
| png_size_t rowbytes = png_get_rowbytes (png, info); | |
| if (rowbytes < (png_size_t) w * 4u) | |
| { | |
| png_destroy_read_struct (&png, &info, nullptr); | |
| hb_raster_png_read_blob_fini (reader); | |
| return false; | |
| } | |
| size_t rgba_size = (size_t) rowbytes * (size_t) h; | |
| if ((h && rgba_size / (size_t) h != (size_t) rowbytes) || | |
| rgba_size > HB_RASTER_MAX_BUFFER_SIZE) | |
| { | |
| png_destroy_read_struct (&png, &info, nullptr); | |
| hb_raster_png_read_blob_fini (reader); | |
| return false; | |
| } | |
| reader->rgba = (uint8_t *) hb_malloc (rgba_size); | |
| if (!reader->rgba) | |
| { | |
| png_destroy_read_struct (&png, &info, nullptr); | |
| hb_raster_png_read_blob_fini (reader); | |
| return false; | |
| } | |
| size_t rows_size = (size_t) h * sizeof (png_bytep); | |
| if (h && rows_size / (size_t) h != sizeof (png_bytep)) | |
| { | |
| png_destroy_read_struct (&png, &info, nullptr); | |
| hb_raster_png_read_blob_fini (reader); | |
| return false; | |
| } | |
| reader->rows = (png_bytep *) hb_malloc (rows_size); | |
| if (!reader->rows) | |
| { | |
| png_destroy_read_struct (&png, &info, nullptr); | |
| hb_raster_png_read_blob_fini (reader); | |
| return false; | |
| } | |
| for (unsigned y = 0; y < (unsigned) h; y++) | |
| reader->rows[y] = (png_bytep) (reader->rgba + (size_t) y * (size_t) rowbytes); | |
| png_read_image (png, reader->rows); | |
| png_read_end (png, nullptr); | |
| png_destroy_read_struct (&png, &info, nullptr); | |
| hb_raster_image_t decoded; | |
| hb_raster_extents_t decoded_extents = {0, 0, (unsigned) w, (unsigned) h, 0}; | |
| if (!decoded.configure (HB_RASTER_FORMAT_BGRA32, decoded_extents)) | |
| { | |
| hb_raster_png_read_blob_fini (reader); | |
| return false; | |
| } | |
| for (unsigned y = 0; y < (unsigned) h; y++) | |
| { | |
| hb_packed_t<uint32_t> *dst = (hb_packed_t<uint32_t> *) (decoded.buffer.arrayZ + (size_t) ((unsigned) h - 1 - y) * decoded.extents.stride); | |
| const uint8_t *src = reader->rgba + (size_t) y * (size_t) rowbytes; | |
| for (unsigned x = 0; x < (unsigned) w; x++) | |
| { | |
| uint8_t r = src[4 * x + 0]; | |
| uint8_t g = src[4 * x + 1]; | |
| uint8_t b = src[4 * x + 2]; | |
| uint8_t a = src[4 * x + 3]; | |
| dst[x] = hb_packed_t<uint32_t> ((uint32_t) hb_raster_div255 (b * a) | |
| | ((uint32_t) hb_raster_div255 (g * a) << 8) | |
| | ((uint32_t) hb_raster_div255 (r * a) << 16) | |
| | ((uint32_t) a << 24)); | |
| } | |
| } | |
| hb_swap (buffer, decoded.buffer); | |
| hb_swap (this->extents, decoded.extents); | |
| hb_swap (format, decoded.format); | |
| hb_raster_png_read_blob_fini (reader); | |
| return true; | |
| #endif | |
| } | |
| hb_blob_t * | |
| hb_raster_image_t::serialize_to_png_or_fail () const | |
| { | |
| #ifndef HAVE_PNG | |
| return nullptr; | |
| #else | |
| if (format != HB_RASTER_FORMAT_BGRA32 || !extents.width || !extents.height) | |
| return nullptr; | |
| png_structp png = png_create_write_struct (PNG_LIBPNG_VER_STRING, nullptr, | |
| hb_raster_png_error, | |
| hb_raster_png_warning); | |
| if (!png) | |
| return nullptr; | |
| png_infop info = png_create_info_struct (png); | |
| if (!info) | |
| { | |
| png_destroy_write_struct (&png, nullptr); | |
| return nullptr; | |
| } | |
| hb_raster_png_write_blob_t *writer = (hb_raster_png_write_blob_t *) hb_calloc (1, sizeof (*writer)); | |
| if (!writer) | |
| { | |
| png_destroy_write_struct (&png, &info); | |
| return nullptr; | |
| } | |
| png_set_write_fn (png, writer, hb_raster_png_write_blob, hb_raster_png_flush_blob); | |
| if (setjmp (png_jmpbuf (png))) | |
| { | |
| png_destroy_write_struct (&png, &info); | |
| hb_raster_png_write_blob_fini (writer); | |
| return nullptr; | |
| } | |
| png_set_IHDR (png, info, | |
| extents.width, extents.height, | |
| 8, PNG_COLOR_TYPE_RGBA, | |
| PNG_INTERLACE_NONE, | |
| PNG_COMPRESSION_TYPE_DEFAULT, | |
| PNG_FILTER_TYPE_DEFAULT); | |
| png_write_info (png, info); | |
| size_t rowbytes = (size_t) extents.width * 4u; | |
| size_t rgba_size = rowbytes * (size_t) extents.height; | |
| if (extents.height && rgba_size / (size_t) extents.height != rowbytes) | |
| { | |
| png_destroy_write_struct (&png, &info); | |
| hb_raster_png_write_blob_fini (writer); | |
| return nullptr; | |
| } | |
| writer->rgba = (uint8_t *) hb_malloc (rgba_size); | |
| if (!writer->rgba) | |
| { | |
| png_destroy_write_struct (&png, &info); | |
| hb_raster_png_write_blob_fini (writer); | |
| return nullptr; | |
| } | |
| size_t rows_size = (size_t) extents.height * sizeof (png_bytep); | |
| if (extents.height && rows_size / (size_t) extents.height != sizeof (png_bytep)) | |
| { | |
| png_destroy_write_struct (&png, &info); | |
| hb_raster_png_write_blob_fini (writer); | |
| return nullptr; | |
| } | |
| writer->rows = (png_bytep *) hb_malloc (rows_size); | |
| if (!writer->rows) | |
| { | |
| png_destroy_write_struct (&png, &info); | |
| hb_raster_png_write_blob_fini (writer); | |
| return nullptr; | |
| } | |
| for (unsigned y = 0; y < extents.height; y++) | |
| { | |
| uint8_t *dst = writer->rgba + (size_t) y * rowbytes; | |
| const uint8_t *src = buffer.arrayZ + (size_t) (extents.height - 1 - y) * extents.stride; | |
| for (unsigned x = 0; x < extents.width; x++) | |
| { | |
| uint32_t px; | |
| hb_memcpy (&px, src + x * 4, 4); | |
| uint8_t b = (uint8_t) (px & 0xFF); | |
| uint8_t g = (uint8_t) ((px >> 8) & 0xFF); | |
| uint8_t r = (uint8_t) ((px >> 16) & 0xFF); | |
| uint8_t a = (uint8_t) (px >> 24); | |
| dst[4 * x + 3] = a; | |
| if (a) | |
| { | |
| dst[4 * x + 0] = (uint8_t) hb_min (255u, ((unsigned) r * 255u + a / 2u) / a); | |
| dst[4 * x + 1] = (uint8_t) hb_min (255u, ((unsigned) g * 255u + a / 2u) / a); | |
| dst[4 * x + 2] = (uint8_t) hb_min (255u, ((unsigned) b * 255u + a / 2u) / a); | |
| } | |
| else | |
| dst[4 * x + 0] = dst[4 * x + 1] = dst[4 * x + 2] = 0; | |
| } | |
| writer->rows[y] = (png_bytep) dst; | |
| } | |
| png_write_image (png, writer->rows); | |
| png_write_end (png, info); | |
| png_destroy_write_struct (&png, &info); | |
| if (writer->length > (size_t) (unsigned) -1) | |
| { | |
| hb_raster_png_write_blob_fini (writer); | |
| return nullptr; | |
| } | |
| unsigned length = (unsigned) writer->length; | |
| char *data = writer->data; | |
| writer->data = nullptr; | |
| hb_raster_png_write_blob_fini (writer); | |
| if (!data && length) | |
| return nullptr; | |
| hb_blob_t *blob = hb_blob_create_or_fail (data, length, | |
| HB_MEMORY_MODE_WRITABLE, | |
| data, hb_free); | |
| if (!blob) | |
| hb_free (data); | |
| return blob; | |
| #endif | |
| } | |
| void | |
| hb_raster_image_t::clear () | |
| { | |
| size_t buf_size = (size_t) extents.stride * extents.height; | |
| hb_memset (buffer.arrayZ, 0, buf_size); | |
| } | |
| const uint8_t * | |
| hb_raster_image_t::get_buffer () const | |
| { | |
| return buffer.arrayZ; | |
| } | |
| void | |
| hb_raster_image_t::composite_from (const hb_raster_image_t *src, | |
| hb_paint_composite_mode_t mode) | |
| { | |
| unsigned w = extents.width; | |
| unsigned h = extents.height; | |
| unsigned stride = extents.stride; | |
| for (unsigned y = 0; y < h; y++) | |
| { | |
| hb_packed_t<uint32_t> *dp = (hb_packed_t<uint32_t> *) (buffer.arrayZ + y * stride); | |
| const hb_packed_t<uint32_t> *sp = (const hb_packed_t<uint32_t> *) (src->buffer.arrayZ + y * stride); | |
| for (unsigned x = 0; x < w; x++) | |
| dp[x] = hb_packed_t<uint32_t> (composite_pixel ((uint32_t) sp[x], (uint32_t) dp[x], mode)); | |
| } | |
| } | |
| /* Composite src image onto dst image. | |
| * Both images must have the same extents and BGRA32 format. */ | |
| void | |
| hb_raster_image_composite (hb_raster_image_t *dst, | |
| const hb_raster_image_t *src, | |
| hb_paint_composite_mode_t mode) | |
| { | |
| dst->composite_from (src, mode); | |
| } | |
| /** | |
| * hb_raster_image_create_or_fail: | |
| * | |
| * Creates a new raster image object. | |
| * | |
| * Return value: (transfer full): | |
| * A newly allocated #hb_raster_image_t with a reference count of 1, | |
| * or `NULL` on allocation failure. | |
| * | |
| * The returned image can be released with hb_raster_image_destroy(), or | |
| * transferred for reuse with hb_raster_draw_recycle_image() or | |
| * hb_raster_paint_recycle_image(). | |
| * | |
| * Since: 13.0.0 | |
| **/ | |
| hb_raster_image_t * | |
| hb_raster_image_create_or_fail (void) | |
| { | |
| return hb_object_create<hb_raster_image_t> (); | |
| } | |
| /** | |
| * hb_raster_image_reference: (skip) | |
| * @image: a raster image | |
| * | |
| * Increases the reference count on @image by one. | |
| * | |
| * This prevents @image from being destroyed until a matching | |
| * call to hb_raster_image_destroy() is made. | |
| * | |
| * Return value: (transfer full): | |
| * The referenced #hb_raster_image_t. | |
| * | |
| * Since: 13.0.0 | |
| **/ | |
| hb_raster_image_t * | |
| hb_raster_image_reference (hb_raster_image_t *image) | |
| { | |
| return hb_object_reference (image); | |
| } | |
| /** | |
| * hb_raster_image_destroy: (skip) | |
| * @image: a raster image | |
| * | |
| * Decreases the reference count on @image by one. When the | |
| * reference count reaches zero, the image and its pixel buffer | |
| * are freed. | |
| * | |
| * Since: 13.0.0 | |
| **/ | |
| void | |
| hb_raster_image_destroy (hb_raster_image_t *image) | |
| { | |
| if (!hb_object_destroy (image)) return; | |
| hb_free (image); | |
| } | |
| /** | |
| * hb_raster_image_set_user_data: (skip) | |
| * @image: a raster image | |
| * @key: the user-data key | |
| * @data: a pointer to the user data | |
| * @destroy: (nullable): a callback to call when @data is not needed anymore | |
| * @replace: whether to replace an existing data with the same key | |
| * | |
| * Attaches a user-data key/data pair to the specified raster image. | |
| * | |
| * Return value: `true` if success, `false` otherwise | |
| * | |
| * Since: 13.0.0 | |
| **/ | |
| hb_bool_t | |
| hb_raster_image_set_user_data (hb_raster_image_t *image, | |
| hb_user_data_key_t *key, | |
| void *data, | |
| hb_destroy_func_t destroy, | |
| hb_bool_t replace) | |
| { | |
| return hb_object_set_user_data (image, key, data, destroy, replace); | |
| } | |
| /** | |
| * hb_raster_image_get_user_data: (skip) | |
| * @image: a raster image | |
| * @key: the user-data key | |
| * | |
| * Fetches the user-data associated with the specified key, | |
| * attached to the specified raster image. | |
| * | |
| * Return value: (transfer none): | |
| * A pointer to the user data | |
| * | |
| * Since: 13.0.0 | |
| **/ | |
| void * | |
| hb_raster_image_get_user_data (const hb_raster_image_t *image, | |
| hb_user_data_key_t *key) | |
| { | |
| return hb_object_get_user_data (image, key); | |
| } | |
| /** | |
| * hb_raster_image_configure: | |
| * @image: a raster image | |
| * @format: the pixel format | |
| * @extents: (nullable): desired image extents | |
| * | |
| * Configures @image format and extents together, resizing backing storage | |
| * at most once. This function does not clear pixel contents. | |
| * | |
| * Passing `NULL` for @extents clears extents and releases the backing | |
| * allocation. | |
| * | |
| * Return value: `true` if configuration succeeds, `false` on allocation | |
| * failure | |
| * | |
| * Since: 13.0.0 | |
| **/ | |
| hb_bool_t | |
| hb_raster_image_configure (hb_raster_image_t *image, | |
| hb_raster_format_t format, | |
| const hb_raster_extents_t *extents) | |
| { | |
| if (unlikely (!extents)) | |
| { | |
| image->extents = {}; | |
| image->buffer.resize_exact (0); | |
| return true; | |
| } | |
| return image->configure (format, *extents); | |
| } | |
| /** | |
| * hb_raster_image_clear: | |
| * @image: a raster image | |
| * | |
| * Clears @image pixels to zero while keeping current extents and format. | |
| * | |
| * Since: 13.0.0 | |
| **/ | |
| void | |
| hb_raster_image_clear (hb_raster_image_t *image) | |
| { | |
| image->clear (); | |
| } | |
| /** | |
| * hb_raster_image_get_buffer: | |
| * @image: a raster image | |
| * | |
| * Fetches the raw pixel buffer of @image. The buffer layout is | |
| * described by the extents obtained from hb_raster_image_get_extents() | |
| * and the format from hb_raster_image_get_format(). Rows are stored | |
| * bottom-to-top. | |
| * | |
| * Return value: (transfer none) (array): | |
| * The pixel buffer, or `NULL` | |
| * | |
| * Since: 13.0.0 | |
| **/ | |
| const uint8_t * | |
| hb_raster_image_get_buffer (const hb_raster_image_t *image) | |
| { | |
| return image->get_buffer (); | |
| } | |
| /** | |
| * hb_raster_image_get_extents: | |
| * @image: a raster image | |
| * @extents: (out) (nullable): the image extents | |
| * | |
| * Fetches the pixel-buffer extents of @image. | |
| * | |
| * Since: 13.0.0 | |
| **/ | |
| void | |
| hb_raster_image_get_extents (const hb_raster_image_t *image, | |
| hb_raster_extents_t *extents) | |
| { | |
| if (extents) | |
| *extents = image->extents; | |
| } | |
| /** | |
| * hb_raster_image_get_format: | |
| * @image: a raster image | |
| * | |
| * Fetches the pixel format of @image. | |
| * | |
| * Return value: | |
| * The #hb_raster_format_t of the image | |
| * | |
| * Since: 13.0.0 | |
| **/ | |
| hb_raster_format_t | |
| hb_raster_image_get_format (const hb_raster_image_t *image) | |
| { | |
| return image->format; | |
| } | |
| /** | |
| * hb_raster_image_deserialize_from_png_or_fail: | |
| * @image: a raster image | |
| * @png: PNG data | |
| * | |
| * Replaces @image contents by deserializing a PNG blob into a | |
| * #HB_RASTER_FORMAT_BGRA32 raster image. | |
| * | |
| * On success, @image extents are reset to pixel extents with origin | |
| * `(0, 0)`. Rows in the resulting image buffer are stored bottom-to-top. | |
| * On failure, @image is left unchanged. | |
| * | |
| * Return value: `true` if deserialization succeeded, `false` otherwise | |
| * | |
| * Since: 13.1.0 | |
| **/ | |
| hb_bool_t | |
| hb_raster_image_deserialize_from_png_or_fail (hb_raster_image_t *image, | |
| hb_blob_t *png) | |
| { | |
| return image->deserialize_from_png (png); | |
| } | |
| /** | |
| * hb_raster_image_serialize_to_png_or_fail: | |
| * @image: a raster image | |
| * | |
| * Serializes @image to a PNG blob. | |
| * | |
| * Currently only #HB_RASTER_FORMAT_BGRA32 images are supported. | |
| * | |
| * Return value: (transfer full): | |
| * A newly allocated PNG #hb_blob_t, or `NULL` on failure | |
| * | |
| * Since: 13.1.0 | |
| **/ | |
| hb_blob_t * | |
| hb_raster_image_serialize_to_png_or_fail (const hb_raster_image_t *image) | |
| { | |
| return image->serialize_to_png_or_fail (); | |
| } |