/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. * SPDX-FileCopyrightText: 2024 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup imbuf */ #include "BLI_array.hh" #include "BLI_rect.h" #include "BLI_task.hh" #include "IMB_filter.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" #include "IMB_colormanagement.hh" #include "MEM_guardedalloc.h" #include "OCIO_colorspace.hh" namespace blender { /* -------------------------------------------------------------------- */ /** \name Generic Buffer Conversion * \{ */ MINLINE uchar ftochar(float value) { return unit_float_to_uchar_clamp(value); } MINLINE void float_to_byte_dither_v4(uchar b[4], const float f[4], float dither, int x, int y) { float dither_value = dither_random_value(x, y) * 0.0033f * dither; b[0] = ftochar(dither_value + f[0]); b[1] = ftochar(dither_value + f[1]); b[2] = ftochar(dither_value + f[2]); b[3] = unit_float_to_uchar_clamp(f[3]); } bool IMB_alpha_affects_rgb(const ImBuf *ibuf) { return ibuf && !flag_is_set(ibuf->flags, ImBufFlags::AlphaChannelPacked); } void IMB_buffer_byte_from_float(uchar *dest, const float *src, int src_channels, float dither, bool predivide, int width, int height, int stride, int start_y) { for (int y = 0; y < height; y++) { const float *from = src + size_t(stride) * y * src_channels; uchar *to = dest + size_t(stride) * y * 4; if (src_channels == 1) { /* single channel input */ for (int x = 0; x < width; x++, from++, to += 4) { to[0] = to[1] = to[2] = to[3] = unit_float_to_uchar_clamp(from[0]); } } else if (src_channels == 3) { /* RGB input */ for (int x = 0; x < width; x++, from += 3, to += 4) { rgb_float_to_uchar(to, from); to[3] = 255; } } else if (src_channels == 4) { /* RGBA input */ if (dither && predivide) { float straight[4]; for (int x = 0; x < width; x++, from += 4, to += 4) { premul_to_straight_v4_v4(straight, from); float_to_byte_dither_v4(to, straight, dither, x, y + start_y); } } else if (dither) { for (int x = 0; x < width; x++, from += 4, to += 4) { float_to_byte_dither_v4(to, from, dither, x, y + start_y); } } else if (predivide) { for (int x = 0; x < width; x++, from += 4, to += 4) { premul_float_to_straight_uchar(to, from); } } else { for (int x = 0; x < width; x++, from += 4, to += 4) { rgba_float_to_uchar(to, from); } } } } } void IMB_buffer_byte_from_float_mask(uchar *dest, const float *src, int src_channels, float dither, int width, int height, const char *mask) { for (int y = 0; y < height; y++) { const float *from = src + size_t(width) * y * src_channels; uchar *to = dest + size_t(width) * y * 4; if (src_channels == 1) { /* single channel input */ for (int x = 0; x < width; x++, from++, to += 4) { if (*mask++ == FILTER_MASK_USED) { to[0] = to[1] = to[2] = to[3] = unit_float_to_uchar_clamp(from[0]); } } } else if (src_channels == 3) { /* RGB input */ for (int x = 0; x < width; x++, from += 3, to += 4) { if (*mask++ == FILTER_MASK_USED) { rgb_float_to_uchar(to, from); to[3] = 255; } } } else if (src_channels == 4) { /* RGBA input */ if (dither) { for (int x = 0; x < width; x++, from += 4, to += 4) { if (*mask++ == FILTER_MASK_USED) { float_to_byte_dither_v4(to, from, dither, x, y); } } } else { for (int x = 0; x < width; x++, from += 4, to += 4) { if (*mask++ == FILTER_MASK_USED) { rgba_float_to_uchar(to, from); } } } } } } void IMB_buffer_float_from_byte( float *dest, const uchar *src, int width, int height, int dest_stride, int src_stride) { for (int y = 0; y < height; y++) { const uchar *from = src + size_t(src_stride) * y * 4; float *to = dest + size_t(dest_stride) * y * 4; for (int x = 0; x < width; x++, from += 4, to += 4) { rgba_uchar_to_float(to, from); } } } void IMB_buffer_float_rgba_from_float( float *dest, const float *src, int src_channels, int width, int height) { if (src_channels == 1) { /* single channel input */ for (int y = 0; y < height; y++) { const float *from = src + size_t(width) * y; float *to = dest + size_t(width) * y * 4; for (int x = 0; x < width; x++, from++, to += 4) { to[0] = to[1] = to[2] = to[3] = from[0]; } } } else if (src_channels == 3) { /* RGB input */ for (int y = 0; y < height; y++) { const float *from = src + size_t(width) * y * 3; float *to = dest + size_t(width) * y * 4; for (int x = 0; x < width; x++, from += 3, to += 4) { copy_v3_v3(to, from); to[3] = 1.0f; } } } else if (src_channels == 4) { /* RGBA input */ for (int y = 0; y < height; y++) { const float *from = src + size_t(width) * y * 4; float *to = dest + size_t(width) * y * 4; memcpy(to, from, sizeof(float) * size_t(4) * width); } } } void IMB_buffer_float_rgba_from_float_mask( float *dest, const float *src, int src_channels, int width, int height, const char *mask) { if (src_channels == 1) { /* single channel input */ for (int y = 0; y < height; y++) { const float *from = src + size_t(width) * y; float *to = dest + size_t(width) * y * 4; for (int x = 0; x < width; x++, from++, to += 4) { if (*mask++ == FILTER_MASK_USED) { to[0] = to[1] = to[2] = to[3] = from[0]; } } } } else if (src_channels == 3) { /* RGB input */ for (int y = 0; y < height; y++) { const float *from = src + size_t(width) * y * 3; float *to = dest + size_t(width) * y * 4; for (int x = 0; x < width; x++, from += 3, to += 4) { if (*mask++ == FILTER_MASK_USED) { copy_v3_v3(to, from); to[3] = 1.0f; } } } } else if (src_channels == 4) { /* RGBA input */ for (int y = 0; y < height; y++) { const float *from = src + size_t(width) * y * 4; float *to = dest + size_t(width) * y * 4; for (int x = 0; x < width; x++, from += 4, to += 4) { if (*mask++ == FILTER_MASK_USED) { copy_v4_v4(to, from); } } } } } void IMB_buffer_float_rgba_srgb_to_linear(float *buffer, int width, int height) { threading::parallel_for(IndexRange(height), 64, [&](const IndexRange y_range) { for (int y : y_range) { float *ptr = buffer + size_t(width) * y * 4; for (int x = 0; x < width; x++, ptr += 4) { srgb_to_linearrgb_predivide_v4(ptr, ptr); } } }); } /** \} */ /* -------------------------------------------------------------------- */ /** \name ImBuf Conversion * \{ */ void IMB_byte_from_float(ImBuf *ibuf) { /* Nothing to do if there's no float buffer */ const float *float_data = ibuf->float_data(); if (float_data == nullptr) { return; } /* Allocate byte buffer if needed. */ if (ibuf->byte_data() == nullptr) { if (!IMB_alloc_byte_pixels(ibuf, false)) { return; } } const char *from_colorspace = IMB_colormanagement_get_float_colorspace(ibuf); const char *to_colorspace = IMB_colormanagement_get_byte_colorspace(ibuf); const bool predivide = IMB_alpha_affects_rgb(ibuf); ColormanageProcessor processor = ColormanageProcessor::colorspace_processor_new(from_colorspace, to_colorspace); /* At 4 floats per pixel, this is 32KB of data, and fits into typical CPU L1 cache. */ static constexpr int grain_size = 2048; uchar *byte_data = ibuf->byte_data_for_write(); threading::parallel_for( IndexRange(IMB_get_pixel_count(ibuf)), grain_size, [&](const IndexRange range) { /* Copy chunk of source float pixels into a local buffer. */ Array buffer(range.size() * ibuf->channels); buffer.as_mutable_span().copy_from( Span(float_data + range.first() * ibuf->channels, buffer.size())); /* Unpremultiply alpha if needed. */ if (predivide) { IMB_unpremultiply_rect_float(buffer.data(), ibuf->channels, range.size(), 1); } /* Convert to byte color space if needed. */ if (!processor.is_noop()) { processor.apply(buffer.data(), range.size(), 1, ibuf->channels, false); } /* Convert to bytes. */ IMB_buffer_byte_from_float(byte_data + range.first() * 4, buffer.data(), ibuf->channels, ibuf->dither, false, range.size(), 1, ibuf->x); }); /* ensure user flag is reset */ ibuf->userflags &= ~IB_RECT_INVALID; } void IMB_float_from_byte_ex(ImBuf *dst, const ImBuf *src, const rcti *region_to_update) { BLI_assert_msg(dst->float_data() != nullptr, "Destination buffer should have a float buffer assigned."); BLI_assert_msg(src->byte_data() != nullptr, "Source buffer should have a byte buffer assigned."); BLI_assert_msg(dst->x == src->x, "Source and destination buffer should have the same dimension"); BLI_assert_msg(dst->y == src->y, "Source and destination buffer should have the same dimension"); BLI_assert_msg(dst->channels = 4, "Destination buffer should have 4 channels."); BLI_assert_msg(region_to_update->xmin >= 0, "Region to update should be clipped to the given buffers."); BLI_assert_msg(region_to_update->ymin >= 0, "Region to update should be clipped to the given buffers."); BLI_assert_msg(region_to_update->xmax <= dst->x, "Region to update should be clipped to the given buffers."); BLI_assert_msg(region_to_update->ymax <= dst->y, "Region to update should be clipped to the given buffers."); const int region_width = BLI_rcti_size_x(region_to_update); const int region_height = BLI_rcti_size_y(region_to_update); const bool premultiply_alpha = IMB_alpha_affects_rgb(src); const uchar *byte_data = src->byte_data(); float *float_data = dst->float_data_for_write(); threading::parallel_for( IndexRange(region_to_update->ymin, region_height), 64, [&](const IndexRange y_range) { const uchar *src_ptr = byte_data + (region_to_update->xmin + y_range.first() * dst->x) * 4; float *dst_ptr = float_data + (region_to_update->xmin + y_range.first() * dst->x) * 4; /* Convert byte -> float without color or alpha conversions. */ IMB_buffer_float_from_byte(dst_ptr, src_ptr, region_width, y_range.size(), src->x, dst->x); /* Convert to scene linear color space, and premultiply alpha if needed. */ float *dst_ptr_line = dst_ptr; for ([[maybe_unused]] const int64_t y : y_range) { IMB_colormanagement_colorspace_to_scene_linear( dst_ptr_line, region_width, 1, dst->channels, src->byte_buffer.colorspace, false); if (premultiply_alpha) { IMB_premultiply_rect_float(dst_ptr_line, dst->channels, region_width, 1); } dst_ptr_line += 4 * dst->x; } }); } void IMB_float_from_byte(ImBuf *ibuf) { /* Nothing to do if there's no byte buffer. */ if (ibuf->byte_data() == nullptr) { return; } /* Allocate float buffer if needed. */ if (ibuf->float_data() == nullptr) { if (!IMB_alloc_float_pixels(ibuf, 4, false)) { return; } } rcti region_to_update; BLI_rcti_init(®ion_to_update, 0, ibuf->x, 0, ibuf->y); IMB_float_from_byte_ex(ibuf, ibuf, ®ion_to_update); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Color to Gray-Scale * \{ */ void IMB_color_to_bw(ImBuf *ibuf) { float *rct_fl = ibuf->float_data_for_write(); uchar *rct = ibuf->byte_data_for_write(); size_t i; if (rct_fl) { if (ibuf->channels >= 3) { for (i = IMB_get_pixel_count(ibuf); i > 0; i--, rct_fl += ibuf->channels) { rct_fl[0] = rct_fl[1] = rct_fl[2] = IMB_colormanagement_get_luminance(rct_fl); } } } if (rct) { for (i = IMB_get_pixel_count(ibuf); i > 0; i--, rct += 4) { rct[0] = rct[1] = rct[2] = IMB_colormanagement_get_luminance_byte(rct); } } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Alter Saturation * \{ */ void IMB_saturation(ImBuf *ibuf, float sat) { const size_t pixel_count = IMB_get_pixel_count(ibuf); if (uchar *byte_data = ibuf->byte_data_for_write()) { threading::parallel_for(IndexRange(pixel_count), 64 * 1024, [&](IndexRange range) { uchar *ptr = byte_data + range.first() * 4; float rgb[3]; float hsv[3]; for ([[maybe_unused]] const int64_t i : range) { rgb_uchar_to_float(rgb, ptr); rgb_to_hsv_v(rgb, hsv); hsv_to_rgb(hsv[0], hsv[1] * sat, hsv[2], rgb + 0, rgb + 1, rgb + 2); rgb_float_to_uchar(ptr, rgb); ptr += 4; } }); } float *float_data = ibuf->float_data_for_write(); if (float_data != nullptr && ibuf->channels >= 3) { threading::parallel_for(IndexRange(pixel_count), 64 * 1024, [&](IndexRange range) { const int channels = ibuf->channels; float *ptr = float_data + range.first() * channels; float hsv[3]; for ([[maybe_unused]] const int64_t i : range) { rgb_to_hsv_v(ptr, hsv); hsv_to_rgb(hsv[0], hsv[1] * sat, hsv[2], ptr + 0, ptr + 1, ptr + 2); ptr += channels; } }); } } /** \} */ } // namespace blender