/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikabrush-mipmap.c * Copyright (C) 2020 Ell * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "libpikamath/pikamath.h" extern "C" { #include "core-types.h" #include "pikabrush.h" #include "pikabrush-mipmap.h" #include "pikabrush-private.h" #include "pikatempbuf.h" } /* extern "C" */ #define PIXELS_PER_THREAD \ (/* each thread costs as much as */ 64.0 * 64.0 /* pixels */) #define PIKA_BRUSH_MIPMAP(brush, mipmaps, x, y) \ ((*(mipmaps))[(y) * (brush)->priv->n_horz_mipmaps + (x)]) /* local function prototypes */ static void pika_brush_mipmap_clear (PikaBrush *brush, PikaTempBuf ***mipmaps); static const PikaTempBuf * pika_brush_mipmap_get (PikaBrush *brush, const PikaTempBuf *source, PikaTempBuf ***mipmaps, gdouble *scale_x, gdouble *scale_y); static PikaTempBuf * pika_brush_mipmap_downscale (const PikaTempBuf *source); static PikaTempBuf * pika_brush_mipmap_downscale_horz (const PikaTempBuf *source); static PikaTempBuf * pika_brush_mipmap_downscale_vert (const PikaTempBuf *source); /* private functions */ static void pika_brush_mipmap_clear (PikaBrush *brush, PikaTempBuf ***mipmaps) { if (*mipmaps) { gint i; for (i = 0; i < brush->priv->n_horz_mipmaps * brush->priv->n_vert_mipmaps; i++) { g_clear_pointer (&(*mipmaps)[i], pika_temp_buf_unref); } g_clear_pointer (mipmaps, g_free); } } static const PikaTempBuf * pika_brush_mipmap_get (PikaBrush *brush, const PikaTempBuf *source, PikaTempBuf ***mipmaps, gdouble *scale_x, gdouble *scale_y) { gint x; gint y; gint i; if (! source) return NULL; if (! *mipmaps) { gint width = pika_temp_buf_get_width (source); gint height = pika_temp_buf_get_height (source); brush->priv->n_horz_mipmaps = floor (log (width) / M_LN2) + 1; brush->priv->n_vert_mipmaps = floor (log (height) / M_LN2) + 1; *mipmaps = g_new0 (PikaTempBuf *, brush->priv->n_horz_mipmaps * brush->priv->n_vert_mipmaps); PIKA_BRUSH_MIPMAP (brush, mipmaps, 0, 0) = pika_temp_buf_ref (source); } x = floor (SAFE_CLAMP (log (1.0 / MAX (*scale_x, 0.0)) / M_LN2, 0, brush->priv->n_horz_mipmaps - 1)); y = floor (SAFE_CLAMP (log (1.0 / MAX (*scale_y, 0.0)) / M_LN2, 0, brush->priv->n_vert_mipmaps - 1)); *scale_x *= pow (2.0, x); *scale_y *= pow (2.0, y); if (PIKA_BRUSH_MIPMAP (brush, mipmaps, x, y)) return PIKA_BRUSH_MIPMAP (brush, mipmaps, x, y); g_return_val_if_fail (x >= 0 || y >= 0, NULL); for (i = 1; i <= x + y; i++) { gint u = x - i; gint v = y; if (u < 0) { v += u; u = 0; } while (u <= x && v >= 0) { if (PIKA_BRUSH_MIPMAP (brush, mipmaps, u, v)) { for (; x - u > y - v; u++) { PIKA_BRUSH_MIPMAP (brush, mipmaps, u + 1, v) = pika_brush_mipmap_downscale_horz ( PIKA_BRUSH_MIPMAP (brush, mipmaps, u, v)); } for (; y - v > x - u; v++) { PIKA_BRUSH_MIPMAP (brush, mipmaps, u, v + 1) = pika_brush_mipmap_downscale_vert ( PIKA_BRUSH_MIPMAP (brush, mipmaps, u, v)); } for (; u < x; u++, v++) { PIKA_BRUSH_MIPMAP (brush, mipmaps, u + 1, v + 1) = pika_brush_mipmap_downscale ( PIKA_BRUSH_MIPMAP (brush, mipmaps, u, v)); } return PIKA_BRUSH_MIPMAP (brush, mipmaps, u, v); } u++; v--; } } g_return_val_if_reached (NULL); } template struct MipmapTraits; template <> struct MipmapTraits { static guint8 mix (guint8 a, guint8 b) { return ((guint32) a + (guint32) b + 1) / 2; } static guint8 mix (guint8 a, guint8 b, guint8 c, guint8 d) { return ((guint32) a + (guint32) b + (guint32) c + (guint32) d + 2) / 4; } }; template <> struct MipmapTraits { static gfloat mix (gfloat a, gfloat b) { return (a + b) / 2.0; } static gfloat mix (gfloat a, gfloat b, gfloat c, gfloat d) { return (a + b + c + d) / 4.0; } }; template struct MipmapAlgorithms { static PikaTempBuf * downscale (const PikaTempBuf *source) { PikaTempBuf *destination; gint width = pika_temp_buf_get_width (source); gint height = pika_temp_buf_get_height (source); width /= 2; height /= 2; destination = pika_temp_buf_new (width, height, pika_temp_buf_get_format (source)); gegl_parallel_distribute_area ( GEGL_RECTANGLE (0, 0, width, height), PIXELS_PER_THREAD, [=] (const GeglRectangle *area) { const T *src0 = (const T *) pika_temp_buf_get_data (source); T *dest0 = (T *) pika_temp_buf_get_data (destination); gint src_stride = N * pika_temp_buf_get_width (source); gint dest_stride = N * pika_temp_buf_get_width (destination); gint y; src0 += 2 * (area->y * src_stride + N * area->x); dest0 += area->y * dest_stride + N * area->x; for (y = 0; y < area->height; y++) { const T *src = src0; T *dest = dest0; gint x; for (x = 0; x < area->width; x++) { gint c; for (c = 0; c < N; c++) { dest[c] = MipmapTraits::mix (src[c], src[N + c], src[src_stride + c], src[src_stride + N + c]); } src += 2 * N; dest += N; } src0 += 2 * src_stride; dest0 += dest_stride; } }); return destination; } static PikaTempBuf * downscale_horz (const PikaTempBuf *source) { PikaTempBuf *destination; gint width = pika_temp_buf_get_width (source); gint height = pika_temp_buf_get_height (source); width /= 2; destination = pika_temp_buf_new (width, height, pika_temp_buf_get_format (source)); gegl_parallel_distribute_range ( height, PIXELS_PER_THREAD / width, [=] (gint offset, gint size) { const T *src0 = (const T *) pika_temp_buf_get_data (source); T *dest0 = (T *) pika_temp_buf_get_data (destination); gint src_stride = N * pika_temp_buf_get_width (source); gint dest_stride = N * pika_temp_buf_get_width (destination); gint y; src0 += offset * src_stride; dest0 += offset * dest_stride; for (y = 0; y < size; y++) { const T *src = src0; T *dest = dest0; gint x; for (x = 0; x < width; x++) { gint c; for (c = 0; c < N; c++) dest[c] = MipmapTraits::mix (src[c], src[N + c]); src += 2 * N; dest += N; } src0 += src_stride; dest0 += dest_stride; } }); return destination; } static PikaTempBuf * downscale_vert (const PikaTempBuf *source) { PikaTempBuf *destination; gint width = pika_temp_buf_get_width (source); gint height = pika_temp_buf_get_height (source); height /= 2; destination = pika_temp_buf_new (width, height, pika_temp_buf_get_format (source)); gegl_parallel_distribute_range ( width, PIXELS_PER_THREAD / height, [=] (gint offset, gint size) { const T *src0 = (const T *) pika_temp_buf_get_data (source); T *dest0 = (T *) pika_temp_buf_get_data (destination); gint src_stride = N * pika_temp_buf_get_width (source); gint dest_stride = N * pika_temp_buf_get_width (destination); gint x; src0 += offset * N; dest0 += offset * N; for (x = 0; x < size; x++) { const T *src = src0; T *dest = dest0; gint y; for (y = 0; y < height; y++) { gint c; for (c = 0; c < N; c++) dest[c] = MipmapTraits::mix (src[c], src[src_stride + c]); src += 2 * src_stride; dest += dest_stride; } src0 += N; dest0 += N; } }); return destination; } }; template static PikaTempBuf * pika_brush_mipmap_dispatch (const PikaTempBuf *source, Func func) { const Babl *format = pika_temp_buf_get_format (source); const Babl *type; gint n_components; type = babl_format_get_type (format, 0); n_components = babl_format_get_n_components (format); if (type == babl_type ("u8")) { switch (n_components) { case 1: return func (MipmapAlgorithms ()); case 3: return func (MipmapAlgorithms ()); } } else if (type == babl_type ("float")) { switch (n_components) { case 1: return func (MipmapAlgorithms ()); case 3: return func (MipmapAlgorithms ()); } } g_return_val_if_reached (NULL); } static PikaTempBuf * pika_brush_mipmap_downscale (const PikaTempBuf *source) { return pika_brush_mipmap_dispatch ( source, [&] (auto algorithms) { return algorithms.downscale (source); }); } static PikaTempBuf * pika_brush_mipmap_downscale_horz (const PikaTempBuf *source) { return pika_brush_mipmap_dispatch ( source, [&] (auto algorithms) { return algorithms.downscale_horz (source); }); } static PikaTempBuf * pika_brush_mipmap_downscale_vert (const PikaTempBuf *source) { return pika_brush_mipmap_dispatch ( source, [&] (auto algorithms) { return algorithms.downscale_vert (source); }); } /* public functions */ void pika_brush_mipmap_clear (PikaBrush *brush) { pika_brush_mipmap_clear (brush, &brush->priv->mask_mipmaps); pika_brush_mipmap_clear (brush, &brush->priv->pixmap_mipmaps); } const PikaTempBuf * pika_brush_mipmap_get_mask (PikaBrush *brush, gdouble *scale_x, gdouble *scale_y) { return pika_brush_mipmap_get (brush, brush->priv->mask, &brush->priv->mask_mipmaps, scale_x, scale_y); } const PikaTempBuf * pika_brush_mipmap_get_pixmap (PikaBrush *brush, gdouble *scale_x, gdouble *scale_y) { return pika_brush_mipmap_get (brush, brush->priv->pixmap, &brush->priv->pixmap_mipmaps, scale_x, scale_y); } gsize pika_brush_mipmap_get_memsize (PikaBrush *brush) { gsize memsize = 0; if (brush->priv->mask_mipmaps) { gint i; for (i = 1; i < brush->priv->n_horz_mipmaps * brush->priv->n_vert_mipmaps; i++) { memsize += pika_temp_buf_get_memsize (brush->priv->mask_mipmaps[i]); } } if (brush->priv->pixmap_mipmaps) { gint i; for (i = 1; i < brush->priv->n_horz_mipmaps * brush->priv->n_vert_mipmaps; i++) { memsize += pika_temp_buf_get_memsize (brush->priv->pixmap_mipmaps[i]); } } return memsize; }