libheif/pixelimage.cc (967 lines of code) (raw):

/* * HEIF codec. * Copyright (c) 2017 Dirk Farin <dirk.farin@gmail.com> * * This file is part of libheif. * * libheif is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * libheif 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libheif. If not, see <http://www.gnu.org/licenses/>. */ #include "pixelimage.h" #include "common_utils.h" #if HAVE_YUV #include "libyuv.h" #endif #include <cassert> #include <cstring> #include <utility> #include <limits> heif_chroma chroma_from_subsampling(int h, int v) { if (h == 2 && v == 2) { return heif_chroma_420; } else if (h == 2 && v == 1) { return heif_chroma_422; } else if (h == 1 && v == 1) { return heif_chroma_444; } else { assert(false); return heif_chroma_undefined; } } HeifPixelImage::~HeifPixelImage() { for (auto& iter : m_planes) { if(!iter.second.plane_use_external_buf) { delete[] iter.second.allocated_mem; } } } int num_interleaved_pixels_per_plane(heif_chroma chroma) { switch (chroma) { case heif_chroma_undefined: case heif_chroma_monochrome: case heif_chroma_420: case heif_chroma_422: case heif_chroma_444: return 1; case heif_chroma_interleaved_RGB: case heif_chroma_interleaved_RRGGBB_BE: case heif_chroma_interleaved_RRGGBB_LE: return 3; case heif_chroma_interleaved_RGBA: case heif_chroma_interleaved_RRGGBBAA_BE: case heif_chroma_interleaved_RRGGBBAA_LE: return 4; } assert(false); return 0; } bool is_integer_multiple_of_chroma_size(int width, int height, heif_chroma chroma) { switch (chroma) { case heif_chroma_444: case heif_chroma_monochrome: return true; case heif_chroma_422: return (width & 1) == 0; case heif_chroma_420: return (width & 1) == 0 && (height & 1) == 0; default: assert(false); return false; } } std::vector<heif_chroma> get_valid_chroma_values_for_colorspace(heif_colorspace colorspace) { switch (colorspace) { case heif_colorspace_YCbCr: return {heif_chroma_420, heif_chroma_422, heif_chroma_444}; case heif_colorspace_RGB: return {heif_chroma_444, heif_chroma_interleaved_RGB, heif_chroma_interleaved_RGBA, heif_chroma_interleaved_RRGGBB_BE, heif_chroma_interleaved_RRGGBBAA_BE, heif_chroma_interleaved_RRGGBB_LE, heif_chroma_interleaved_RRGGBBAA_LE}; case heif_colorspace_monochrome: return {heif_chroma_monochrome}; default: return {}; } } void HeifPixelImage::create(int width, int height, heif_colorspace colorspace, heif_chroma chroma) { m_width = width; m_height = height; m_colorspace = colorspace; m_chroma = chroma; } static uint32_t rounded_size(uint32_t s) { s = (s + 1U) & ~1U; if (s < 64) { s = 64; } return s; } bool HeifPixelImage::add_plane(heif_channel channel, int width, int height, int bit_depth) { ImagePlane plane; if (plane.alloc(width, height, bit_depth, m_chroma)) { m_planes.insert(std::make_pair(channel, plane)); return true; } else { return false; } } bool HeifPixelImage::ImagePlane::alloc(int width, int height, int bit_depth, heif_chroma chroma) { assert(width >= 0); assert(height >= 0); assert(bit_depth >= 1); assert(bit_depth <= 32); plane_use_external_buf = false; // use 16 byte alignment uint16_t alignment = 16; // must be power of two m_width = width; m_height = height; m_mem_width = rounded_size(width); m_mem_height = rounded_size(height); // for backwards compatibility, allow for 24/32 bits for RGB/RGBA interleaved chromas if (chroma == heif_chroma_interleaved_RGB && bit_depth == 24) { bit_depth = 8; } if (chroma == heif_chroma_interleaved_RGBA && bit_depth == 32) { bit_depth = 8; } assert(m_bit_depth <= 16); m_bit_depth = static_cast<uint8_t>(bit_depth); int bytes_per_component = (m_bit_depth + 7) / 8; int bytes_per_pixel = num_interleaved_pixels_per_plane(chroma) * bytes_per_component; stride = m_mem_width * bytes_per_pixel; stride = (stride + alignment - 1U) & ~(alignment - 1U); try { allocated_mem = new uint8_t[m_mem_height * stride + alignment - 1]; mem = allocated_mem; // shift beginning of image data to aligned memory position auto mem_start_addr = (uint64_t) mem; auto mem_start_offset = (mem_start_addr & (alignment - 1U)); if (mem_start_offset != 0) { mem += alignment - mem_start_offset; } return true; } catch (const std::bad_alloc& excpt) { return false; } } bool HeifPixelImage::add_shared_rgba_plane(heif_channel channel, int width, int height, uint8_t bit_depth) { ImagePlane plane; int bytes_per_component = (bit_depth + 7) / 8; uint32_t allpixel = width * height * bytes_per_component ; switch(m_chroma ) { case heif_chroma_monochrome : allpixel = width * height * bytes_per_component ; break ; case heif_chroma_420 : allpixel = width * height * bytes_per_component * 1.5; break ; case heif_chroma_422 : allpixel = width * height * bytes_per_component * 2 ; break ; case heif_chroma_undefined : case heif_chroma_444 : allpixel = width * height * bytes_per_component * 3 ; break ; case heif_chroma_interleaved_RGB : allpixel = width * height * bytes_per_component * 3 ; break ; case heif_chroma_interleaved_RGBA : allpixel = width * height * bytes_per_component * 4 ; break ; case heif_chroma_interleaved_RRGGBB_BE : allpixel = width * height * bytes_per_component * 6 ; break ; case heif_chroma_interleaved_RRGGBBAA_BE : allpixel = width * height * bytes_per_component * 8 ; break ; case heif_chroma_interleaved_RRGGBB_LE : allpixel = width * height * bytes_per_component * 6 ; break ; case heif_chroma_interleaved_RRGGBBAA_LE : allpixel = width * height * bytes_per_component * 8 ; break ; } if( image_external_buf_base && (allpixel <= image_external_buf_len)) { plane.m_width = width ; plane.m_height= height; plane.m_mem_width = width ; plane.m_mem_height= height; plane.m_bit_depth = bit_depth; plane.stride = image_external_buf_stride ; plane.mem = image_external_buf_base ; plane.allocated_mem = image_external_buf_base ; plane.plane_use_external_buf = true ; m_planes.insert(std::make_pair(channel, plane)); return true; } else { if (plane.alloc(width, height, bit_depth, m_chroma)) { m_planes.insert(std::make_pair(channel , plane)); return true; } else { return false; } } } bool HeifPixelImage::extend_padding_to_size(int width, int height) { for (auto& planeIter : m_planes) { auto* plane = &planeIter.second; int subsampled_width, subsampled_height; get_subsampled_size(width, height, planeIter.first, m_chroma, &subsampled_width, &subsampled_height); int old_width = plane->m_width; int old_height = plane->m_height; if (plane->m_mem_width < subsampled_width || plane->m_mem_height < subsampled_height) { ImagePlane newPlane; if (!newPlane.alloc(subsampled_width, subsampled_height, plane->m_bit_depth, m_chroma)) { return false; } // copy the visible part of the old plane into the new plane for (int y = 0; y < plane->m_height; y++) { memcpy(&newPlane.mem[y * newPlane.stride], &plane->mem[y * plane->stride], plane->m_width); } planeIter.second = newPlane; plane = &planeIter.second; } // extend plane size int bytes_per_pixel = (plane->m_bit_depth + 7) / 8; for (int y = 0; y < old_height; y++) { for (int x = old_width; x < subsampled_width; x++) { memcpy(&plane->mem[y * plane->stride + x * bytes_per_pixel], &plane->mem[y * plane->stride + (plane->m_width - 1) * bytes_per_pixel], bytes_per_pixel); } } for (int y = old_height; y < subsampled_height; y++) { memcpy(&plane->mem[y * plane->stride], &plane->mem[(plane->m_height - 1) * plane->stride], subsampled_width * bytes_per_pixel); } } // don't modify the logical image size return true; } bool HeifPixelImage::has_channel(heif_channel channel) const { return (m_planes.find(channel) != m_planes.end()); } bool HeifPixelImage::has_alpha() const { return has_channel(heif_channel_Alpha) || get_chroma_format() == heif_chroma_interleaved_RGBA || get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE || get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE; } int HeifPixelImage::get_width(enum heif_channel channel) const { auto iter = m_planes.find(channel); if (iter == m_planes.end()) { return -1; } return iter->second.m_width; } int HeifPixelImage::get_height(enum heif_channel channel) const { auto iter = m_planes.find(channel); if (iter == m_planes.end()) { return -1; } return iter->second.m_height; } std::set<heif_channel> HeifPixelImage::get_channel_set() const { std::set<heif_channel> channels; for (const auto& plane : m_planes) { channels.insert(plane.first); } return channels; } uint8_t HeifPixelImage::get_storage_bits_per_pixel(enum heif_channel channel) const { if (channel == heif_channel_interleaved) { auto chroma = get_chroma_format(); switch (chroma) { case heif_chroma_interleaved_RGB: return 24; case heif_chroma_interleaved_RGBA: return 32; case heif_chroma_interleaved_RRGGBB_BE: case heif_chroma_interleaved_RRGGBB_LE: return 48; case heif_chroma_interleaved_RRGGBBAA_BE: case heif_chroma_interleaved_RRGGBBAA_LE: return 64; default: return -1; // invalid channel/chroma specification } } else { uint32_t bpp = (get_bits_per_pixel(channel) + 7U) & ~7U; assert(bpp <= 255); return static_cast<uint8_t>(bpp); } } uint8_t HeifPixelImage::get_bits_per_pixel(enum heif_channel channel) const { auto iter = m_planes.find(channel); if (iter == m_planes.end()) { return -1; } return iter->second.m_bit_depth; } uint8_t* HeifPixelImage::get_plane(enum heif_channel channel, int* out_stride) { auto iter = m_planes.find(channel); if (iter == m_planes.end()) { return nullptr; } if (out_stride) { *out_stride = iter->second.stride; } return iter->second.mem; } const uint8_t* HeifPixelImage::get_plane(enum heif_channel channel, int* out_stride) const { auto iter = m_planes.find(channel); if (iter == m_planes.end()) { return nullptr; } if (out_stride) { *out_stride = iter->second.stride; } return iter->second.mem; } void HeifPixelImage::copy_new_plane_from(const std::shared_ptr<const HeifPixelImage>& src_image, heif_channel src_channel, heif_channel dst_channel) { int width = src_image->get_width(src_channel); int height = src_image->get_height(src_channel); assert(!has_channel(dst_channel)); add_plane(dst_channel, width, height, src_image->get_bits_per_pixel(src_channel)); uint8_t* dst; int dst_stride = 0; const uint8_t* src; int src_stride = 0; src = src_image->get_plane(src_channel, &src_stride); dst = get_plane(dst_channel, &dst_stride); int bpl = width * (src_image->get_storage_bits_per_pixel(src_channel) / 8); for (int y = 0; y < height; y++) { memcpy(dst + y * dst_stride, src + y * src_stride, bpl); } } void HeifPixelImage::fill_new_plane(heif_channel dst_channel, uint16_t value, int width, int height, int bpp) { add_plane(dst_channel, width, height, bpp); int num_interleaved = num_interleaved_pixels_per_plane(m_chroma); if (bpp <= 8) { uint8_t* dst; int dst_stride = 0; dst = get_plane(dst_channel, &dst_stride); int width_bytes = width * num_interleaved; for (int y = 0; y < height; y++) { memset(dst + y * dst_stride, value, width_bytes); } } else { uint16_t* dst; int dst_stride = 0; dst = (uint16_t*) get_plane(dst_channel, &dst_stride); dst_stride /= 2; for (int y = 0; y < height; y++) { for (int x = 0; x < width * num_interleaved; x++) { dst[y * dst_stride + x] = value; } } } } void HeifPixelImage::transfer_plane_from_image_as(const std::shared_ptr<HeifPixelImage>& source, heif_channel src_channel, heif_channel dst_channel) { // TODO: check that dst_channel does not exist yet ImagePlane plane = source->m_planes[src_channel]; source->m_planes.erase(src_channel); m_planes.insert(std::make_pair(dst_channel, plane)); } bool is_chroma_with_alpha(heif_chroma chroma) { switch (chroma) { case heif_chroma_undefined: case heif_chroma_monochrome: case heif_chroma_420: case heif_chroma_422: case heif_chroma_444: case heif_chroma_interleaved_RGB: case heif_chroma_interleaved_RRGGBB_BE: case heif_chroma_interleaved_RRGGBB_LE: return false; case heif_chroma_interleaved_RGBA: case heif_chroma_interleaved_RRGGBBAA_BE: case heif_chroma_interleaved_RRGGBBAA_LE: return true; } assert(false); return false; } Error HeifPixelImage::rotate_ccw(int angle_degrees, std::shared_ptr<HeifPixelImage>& out_img) { // --- create output image (or simply reuse existing image) if (angle_degrees == 0) { out_img = shared_from_this(); return Error::Ok; } int out_width = m_width; int out_height = m_height; if (angle_degrees == 90 || angle_degrees == 270) { std::swap(out_width, out_height); } out_img = std::make_shared<HeifPixelImage>(); out_img->create(out_width, out_height, m_colorspace, m_chroma); // --- rotate all channels for (const auto& plane_pair : m_planes) { heif_channel channel = plane_pair.first; const ImagePlane& plane = plane_pair.second; /* if (plane.bit_depth != 8) { return Error(heif_error_Unsupported_feature, heif_suberror_Unspecified, "Can currently only rotate images with 8 bits per pixel"); } */ int out_plane_width = plane.m_width; int out_plane_height = plane.m_height; if (angle_degrees == 90 || angle_degrees == 270) { std::swap(out_plane_width, out_plane_height); } out_img->add_plane(channel, out_plane_width, out_plane_height, plane.m_bit_depth); int w = plane.m_width; int h = plane.m_height; int in_stride = plane.stride; const uint8_t* in_data = plane.mem; int out_stride = 0; uint8_t* out_data = out_img->get_plane(channel, &out_stride); if (plane.m_bit_depth == 8) { int convert_result = -1; if((this->m_colorspace == heif_colorspace_RGB) && (this->m_chroma == heif_chroma_interleaved_RGBA)) { #if HAVE_YUV libyuv::RotationModeEnum mode ; switch(angle_degrees) { case 0 : mode = libyuv::kRotate0 ; break; case 90 : mode = libyuv::kRotate270; break; case 180: mode = libyuv::kRotate180; break; case 270: mode = libyuv::kRotate90 ; break; } convert_result = libyuv::ARGBRotate(in_data, in_stride, out_data, out_stride, w, h, mode); #endif } if(convert_result < 0 ) { if (angle_degrees == 270) { for (long long x = 0; x < h; x++) for (long long y = 0; y < w; y++) { if(m_chroma >= heif_chroma_interleaved_RGB ) { int channel_num = (m_chroma == heif_chroma_interleaved_RGB) ? 3 : ((m_chroma == heif_chroma_interleaved_RGBA) ? 4 : 1) ; for(int channel = 0; channel < channel_num; channel++) out_data[y * out_stride + x*channel_num + channel] = in_data[(h - 1 - x) * in_stride + y*channel_num + channel]; } else { out_data[y * out_stride + x] = in_data[(h - 1 - x) * in_stride + y]; } } } else if (angle_degrees == 180) { for (long long y = 0; y < h; y++) for (long long x = 0; x < w; x++) { if(m_chroma >= heif_chroma_interleaved_RGB ) { int channel_num = (m_chroma == heif_chroma_interleaved_RGB) ? 3 : ((m_chroma == heif_chroma_interleaved_RGBA) ? 4 : 1) ; for(int channel = 0; channel < channel_num; channel++) out_data[y * out_stride + x*channel_num + channel] = in_data[(h - 1 - y) * in_stride + (w - 1 - x)*channel_num + channel]; } else { out_data[y * out_stride + x] = in_data[(h - 1 - y) * in_stride + (w - 1 - x)]; } } } else if (angle_degrees == 90) { for (long x = 0; x < h; x++) for (long y = 0; y < w; y++) { if(m_chroma >= heif_chroma_interleaved_RGB ) { int channel_num = (m_chroma == heif_chroma_interleaved_RGB) ? 3 : ((m_chroma == heif_chroma_interleaved_RGBA) ? 4 : 1) ; for(int channel = 0; channel < channel_num; channel++) out_data[y * out_stride + x*channel_num + channel] = in_data[x * in_stride + (w - 1 - y)*channel_num + channel]; } else { out_data[y * out_stride + x] = in_data[x * in_stride + (w - 1 - y)]; } } } } } else { // 16 bit (TODO: unchecked code) if (angle_degrees == 270) { for (long long x = 0; x < h; x++) for (long long y = 0; y < w; y++) { if(m_chroma >= heif_chroma_interleaved_RRGGBB_BE ) { int channel_num = 1; if((m_chroma == heif_chroma_interleaved_RRGGBB_BE) || (m_chroma == heif_chroma_interleaved_RRGGBB_LE)) { channel_num = 3; } else if((m_chroma == heif_chroma_interleaved_RRGGBBAA_BE) || (m_chroma == heif_chroma_interleaved_RRGGBBAA_LE)) { channel_num = 4; } else { channel_num = 1; } for(int channel = 0; channel < channel_num; channel++) { out_data[y * out_stride + 2 * x * channel_num + 2 * channel] = in_data[(h - 1 - x) * in_stride + 2 * y * channel_num + 2 * channel]; out_data[y * out_stride + 2 * x * channel_num + 2 * channel + 1] = in_data[(h - 1 - x) * in_stride + 2 * y * channel_num + 2 * channel + 1]; } } else { out_data[y * out_stride + 2 * x] = in_data[(h - 1 - x) * in_stride + 2 * y]; out_data[y * out_stride + 2 * x + 1] = in_data[(h - 1 - x) * in_stride + 2 * y + 1]; } } } else if (angle_degrees == 180) { for (long long y = 0; y < h; y++) for (long long x = 0; x < w; x++) { if(m_chroma >= heif_chroma_interleaved_RRGGBB_BE ) { int channel_num = 1; if((m_chroma == heif_chroma_interleaved_RRGGBB_BE) || (m_chroma == heif_chroma_interleaved_RRGGBB_LE)) { channel_num = 3; } else if((m_chroma == heif_chroma_interleaved_RRGGBBAA_BE) || (m_chroma == heif_chroma_interleaved_RRGGBBAA_LE)) { channel_num = 4; } else { channel_num = 1; } for(int channel = 0; channel < channel_num; channel++) { out_data[y * out_stride + 2 * x * channel_num + 2 * channel] = in_data[(h - 1 - y) * in_stride + 2 * (w - 1 - x) * channel_num + 2 * channel]; out_data[y * out_stride + 2 * x * channel_num + 2 * channel + 1] = in_data[(h - 1 - y) * in_stride + 2 * (w - 1 - x) * channel_num + 2 * channel + 1]; } } else { out_data[y * out_stride + 2 * x] = in_data[(h - 1 - y) * in_stride + 2 * (w - 1 - x)]; out_data[y * out_stride + 2 * x + 1] = in_data[(h - 1 - y) * in_stride + 2 * (w - 1 - x) + 1]; } } } else if (angle_degrees == 90) { for (long long x = 0; x < h; x++) for (long long y = 0; y < w; y++) { if(m_chroma >= heif_chroma_interleaved_RRGGBB_BE ) { int channel_num = 1; if((m_chroma == heif_chroma_interleaved_RRGGBB_BE) || (m_chroma == heif_chroma_interleaved_RRGGBB_LE)) { channel_num = 3; } else if((m_chroma == heif_chroma_interleaved_RRGGBBAA_BE) || (m_chroma == heif_chroma_interleaved_RRGGBBAA_LE)) { channel_num = 4; } else { channel_num = 1; } for(int channel = 0; channel < channel_num; channel++) { out_data[y * out_stride + 2 * x * channel_num + 2 * channel] = in_data[x * in_stride + 2 * (w - 1 - y) * channel_num + 2 * channel]; out_data[y * out_stride + 2 * x * channel_num + 2 * channel + 1] = in_data[x * in_stride + 2 * (w - 1 - y) * channel_num + 2 * channel + 1]; } } else { out_data[y * out_stride + 2 * x] = in_data[x * in_stride + 2 * (w - 1 - y)]; out_data[y * out_stride + 2 * x + 1] = in_data[x * in_stride + 2 * (w - 1 - y) + 1]; } } } } } // --- pass the color profiles to the new image out_img->set_color_profile_nclx(get_color_profile_nclx()); out_img->set_color_profile_icc(get_color_profile_icc()); return Error::Ok; } Error HeifPixelImage::mirror_inplace(heif_transform_mirror_direction direction) { for (auto& plane_pair : m_planes) { ImagePlane& plane = plane_pair.second; if (plane.m_bit_depth != 8) { return Error(heif_error_Unsupported_feature, heif_suberror_Unspecified, "Can currently only mirror images with 8 bits per pixel"); } int w = plane.m_width; int h = plane.m_height; int stride = plane.stride; uint8_t* data = plane.mem; if((this->m_colorspace == heif_colorspace_RGB) && (this->m_chroma == heif_chroma_interleaved_RGBA)) { if (direction == heif_transform_mirror_direction_horizontal) { for (int y = 0; y < h; y++) { for (int x = 0; x < 4 * w / 2; x++) std::swap(data[y * stride + x], data[y * stride + 4 * w - 1 - x]); } } else { for (int y = 0; y < h / 2; y++) { for (int x = 0; x < 4 * w; x++) std::swap(data[y * stride + x], data[(h - 1 - y) * stride + x]); } } } else { if (direction == heif_transform_mirror_direction_horizontal) { for (int y = 0; y < h; y++) { for (int x = 0; x < w / 2; x++) std::swap(data[y * stride + x], data[y * stride + w - 1 - x]); } } else { for (int y = 0; y < h / 2; y++) { for (int x = 0; x < w; x++) std::swap(data[y * stride + x], data[(h - 1 - y) * stride + x]); } } } } return Error::Ok; } Error HeifPixelImage::crop(int left, int right, int top, int bottom, std::shared_ptr<HeifPixelImage>& out_img) const { out_img = std::make_shared<HeifPixelImage>(); out_img->create(right - left + 1, bottom - top + 1, m_colorspace, m_chroma); // --- crop all channels for (const auto& plane_pair : m_planes) { heif_channel channel = plane_pair.first; const ImagePlane& plane = plane_pair.second; if (false && plane.m_bit_depth != 8) { return Error(heif_error_Unsupported_feature, heif_suberror_Unspecified, "Can currently only crop images with 8 bits per pixel"); } int w = plane.m_width; int h = plane.m_height; int plane_left = left * w / m_width; int plane_right = right * w / m_width; int plane_top = top * h / m_height; int plane_bottom = bottom * h / m_height; if((this->m_colorspace == heif_colorspace_RGB) && (this->m_chroma == heif_chroma_interleaved_RGBA)) { out_img->add_plane(channel, (plane_right - plane_left + 1)*4, plane_bottom - plane_top + 1, plane.m_bit_depth); } else { out_img->add_plane(channel, plane_right - plane_left + 1, plane_bottom - plane_top + 1, plane.m_bit_depth); } int in_stride = plane.stride; const uint8_t* in_data = plane.mem; int out_stride = 0; uint8_t* out_data = out_img->get_plane(channel, &out_stride); if((this->m_colorspace == heif_colorspace_RGB) && (this->m_chroma == heif_chroma_interleaved_RGBA)) { if (plane.m_bit_depth == 8) { for (int y = plane_top; y <= plane_bottom; y++) { memcpy(&out_data[(y - plane_top) * out_stride], &in_data[y * in_stride + plane_left * 4], (plane_right - plane_left + 1) * 4 ); } } else { for (int y = plane_top; y <= plane_bottom; y++) { memcpy(&out_data[(y - plane_top) * out_stride], &in_data[y * in_stride + plane_left * 8], (plane_right - plane_left + 1) * 2 * 4); } } } else { if (plane.m_bit_depth == 8) { for (int y = plane_top; y <= plane_bottom; y++) { memcpy(&out_data[(y - plane_top) * out_stride], &in_data[y * in_stride + plane_left], plane_right - plane_left + 1); } } else { for (int y = plane_top; y <= plane_bottom; y++) { memcpy(&out_data[(y - plane_top) * out_stride], &in_data[y * in_stride + plane_left * 2], (plane_right - plane_left + 1) * 2); } } } } // --- pass the color profiles to the new image out_img->set_color_profile_nclx(get_color_profile_nclx()); out_img->set_color_profile_icc(get_color_profile_icc()); return Error::Ok; } #define PREMULTI_PIXEL(dst, src, alpha) \ { \ uint32_t src_in = src; \ uint32_t alpha_in = alpha; \ uint32_t tmp = (src_in * alpha_in + 128) >> 8; \ dst = uint8_t(tmp); \ } Error HeifPixelImage::rgba_premultiply_alpha(void ) { int width = this->m_width ; int height= this->m_height; int bpp = this->get_bits_per_pixel(heif_channel_interleaved); if(!this->has_channel(heif_channel_interleaved) ) { return Error(heif_error_Usage_error, heif_suberror_Unsupported_image_type); } if(this->get_colorspace() == heif_colorspace_undefined || this->get_colorspace() == heif_colorspace_YCbCr ) { return Error(heif_error_Usage_error, heif_suberror_Unsupported_image_type); } uint8_t* rgba_p; int rgba_p_stride = 0; rgba_p = (uint8_t*) this->get_plane(heif_channel_interleaved, &rgba_p_stride); #if HAVE_YUV if(libyuv::ARGBAttenuate(rgba_p, rgba_p_stride, rgba_p, rgba_p_stride, width, height) != 0) { return Error(heif_error_Usage_error, heif_suberror_Unsupported_image_type); } #else uint8_t * rgba_row = rgba_p; for(int h = 0; h < height; h++) { for(int w = 0 ; w < width; w++) { PREMULTI_PIXEL(*(rgba_row + 4 * w + 0), *(rgba_row + 4 * w + 0), *(rgba_row + 4 * w + 3)); PREMULTI_PIXEL(*(rgba_row + 4 * w + 1), *(rgba_row + 4 * w + 1), *(rgba_row + 4 * w + 3)); PREMULTI_PIXEL(*(rgba_row + 4 * w + 2), *(rgba_row + 4 * w + 2), *(rgba_row + 4 * w + 3)); } rgba_row += rgba_p_stride ; } #endif this->set_premultiplied_alpha(true); return Error::Ok; } Error HeifPixelImage::fill_RGB_16bit(uint16_t r, uint16_t g, uint16_t b, uint16_t a) { for (const auto& channel : {heif_channel_R, heif_channel_G, heif_channel_B, heif_channel_Alpha}) { const auto plane_iter = m_planes.find(channel); if (plane_iter == m_planes.end()) { // alpha channel is optional, R,G,B is required if (channel == heif_channel_Alpha) { continue; } return Error(heif_error_Usage_error, heif_suberror_Nonexisting_image_channel_referenced); } ImagePlane& plane = plane_iter->second; if (plane.m_bit_depth != 8) { return Error(heif_error_Unsupported_feature, heif_suberror_Unspecified, "Can currently only fill images with 8 bits per pixel"); } size_t h = plane.m_height; size_t stride = plane.stride; uint8_t* data = plane.mem; uint16_t val16; switch (channel) { case heif_channel_R: val16 = r; break; case heif_channel_G: val16 = g; break; case heif_channel_B: val16 = b; break; case heif_channel_Alpha: val16 = a; break; default: // initialization only to avoid warning of uninitialized variable. val16 = 0; // Should already be detected by the check above ("m_planes.find"). assert(false); } auto val8 = static_cast<uint8_t>(val16 >> 8U); // memset() even when h * stride > sizeof(size_t) if (std::numeric_limits<size_t>::max() / stride > h) { // can fill in one step memset(data, val8, stride * h); } else { // fill line by line auto* p = data; for (size_t y=0;y<h;y++) { memset(p, val8, stride); p += stride; } } } return Error::Ok; } uint32_t negate_negative_int32(int32_t x) { assert(x <= 0); if (x == INT32_MIN) { return static_cast<uint32_t>(INT32_MAX) + 1; } else { return static_cast<uint32_t>(-x); } } Error HeifPixelImage::overlay(std::shared_ptr<HeifPixelImage>& overlay, int32_t dx, int32_t dy) { std::set<enum heif_channel> channels = overlay->get_channel_set(); bool has_alpha = overlay->has_channel(heif_channel_Alpha); //bool has_alpha_me = has_channel(heif_channel_Alpha); int alpha_stride = 0; uint8_t* alpha_p; alpha_p = overlay->get_plane(heif_channel_Alpha, &alpha_stride); for (heif_channel channel : channels) { if (!has_channel(channel)) { continue; } int in_stride = 0; const uint8_t* in_p; int out_stride = 0; uint8_t* out_p; in_p = overlay->get_plane(channel, &in_stride); out_p = get_plane(channel, &out_stride); uint32_t in_w = overlay->get_width(channel); uint32_t in_h = overlay->get_height(channel); uint32_t out_w = get_width(channel); uint32_t out_h = get_height(channel); // top-left points where to start copying in source and destination uint32_t in_x0; uint32_t in_y0; uint32_t out_x0; uint32_t out_y0; if (dx > 0 && static_cast<uint32_t>(dx) >= out_w) { // the overlay image is completely outside the right border -> skip overlaying return Error::Ok; } else if (dx < 0 && in_w <= negate_negative_int32(dx)) { // the overlay image is completely outside the left border -> skip overlaying return Error::Ok; } if (dx < 0) { // overlay image started partially outside of left border in_x0 = negate_negative_int32(dx); out_x0 = 0; in_w = in_w - in_x0; // in_x0 < in_w because in_w > -dx = in_x0 } else { in_x0 = 0; out_x0 = static_cast<uint32_t>(dx); } // we know that dx >= 0 && dx < out_w if (static_cast<uint32_t>(dx) > UINT32_MAX - in_w || dx + in_w > out_w) { // overlay image extends partially outside of right border in_w = out_w - static_cast<uint32_t>(dx); // we know that dx < out_w from first condition } if (dy > 0 && static_cast<uint32_t>(dy) >= out_h) { // the overlay image is completely outside the bottom border -> skip overlaying return Error::Ok; } else if (dy < 0 && in_h <= negate_negative_int32(dy)) { // the overlay image is completely outside the top border -> skip overlaying return Error::Ok; } if (dy < 0) { // overlay image started partially outside of top border in_y0 = negate_negative_int32(dy); out_y0 = 0; in_h = in_h - in_y0; // in_y0 < in_h because in_h > -dy = in_y0 } else { in_y0 = 0; out_y0 = static_cast<uint32_t>(dy); } // we know that dy >= 0 && dy < out_h if (static_cast<uint32_t>(dy) > UINT32_MAX - in_h || dy + in_h > out_h) { // overlay image extends partially outside of bottom border in_h = out_h - static_cast<uint32_t>(dy); // we know that dy < out_h from first condition } for (uint32_t y = in_y0; y < in_h; y++) { if (!has_alpha) { memcpy(out_p + out_x0 + (out_y0 + y - in_y0) * out_stride, in_p + in_x0 + y * in_stride, in_w - in_x0); } else { for (uint32_t x = in_x0; x < in_w; x++) { uint8_t* outptr = &out_p[out_x0 + (out_y0 + y - in_y0) * out_stride + x]; uint8_t in_val = in_p[in_x0 + y * in_stride + x]; uint8_t alpha_val = alpha_p[in_x0 + y * in_stride + x]; *outptr = (uint8_t) ((in_val * alpha_val + *outptr * (255 - alpha_val)) / 255); } } } } return Error::Ok; } Error HeifPixelImage::scale_nearest_neighbor(std::shared_ptr<HeifPixelImage>& out_img, int width, int height) const { out_img = std::make_shared<HeifPixelImage>(); out_img->create(width, height, m_colorspace, m_chroma); // --- create output image with scaled planes if (has_channel(heif_channel_interleaved)) { out_img->add_plane(heif_channel_interleaved, width, height, get_bits_per_pixel(heif_channel_interleaved)); } else { if (get_colorspace() == heif_colorspace_RGB) { if (!has_channel(heif_channel_R) || !has_channel(heif_channel_G) || !has_channel(heif_channel_B)) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "RGB input without R,G,B, planes"); } out_img->add_plane(heif_channel_R, width, height, get_bits_per_pixel(heif_channel_R)); out_img->add_plane(heif_channel_G, width, height, get_bits_per_pixel(heif_channel_G)); out_img->add_plane(heif_channel_B, width, height, get_bits_per_pixel(heif_channel_B)); } else if (get_colorspace() == heif_colorspace_monochrome) { if (!has_channel(heif_channel_Y)) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "monochrome input with no Y plane"); } out_img->add_plane(heif_channel_Y, width, height, get_bits_per_pixel(heif_channel_Y)); } else if (get_colorspace() == heif_colorspace_YCbCr) { if (!has_channel(heif_channel_Y) || !has_channel(heif_channel_Cb) || !has_channel(heif_channel_Cr)) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "YCbCr image without Y,Cb,Cr planes"); } int cw, ch; get_subsampled_size(width, height, heif_channel_Cb, get_chroma_format(), &cw, &ch); out_img->add_plane(heif_channel_Y, width, height, get_bits_per_pixel(heif_channel_Y)); out_img->add_plane(heif_channel_Cb, cw, ch, get_bits_per_pixel(heif_channel_Cb)); out_img->add_plane(heif_channel_Cr, cw, ch, get_bits_per_pixel(heif_channel_Cr)); } else { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "unknown color configuration"); } if (has_channel(heif_channel_Alpha)) { out_img->add_plane(heif_channel_Alpha, width, height, get_bits_per_pixel(heif_channel_Alpha)); } } // --- scale all channels for (const auto& plane_pair : m_planes) { heif_channel channel = plane_pair.first; const ImagePlane& plane = plane_pair.second; const int bpp = get_storage_bits_per_pixel(channel) / 8; if (!out_img->has_channel(channel)) { return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "scaling input has extra color plane"); } int out_w = out_img->get_width(channel); int out_h = out_img->get_height(channel); int in_stride = plane.stride; const uint8_t* in_data = plane.mem; int out_stride = 0; uint8_t* out_data = out_img->get_plane(channel, &out_stride); for (int y = 0; y < out_h; y++) { int iy = y * m_height / height; if (bpp == 1) { for (int x = 0; x < out_w; x++) { int ix = x * m_width / width; out_data[y * out_stride + x] = in_data[iy * in_stride + ix]; } } else { for (int x = 0; x < out_w; x++) { int ix = x * m_width / width; for (int b = 0; b < bpp; b++) { out_data[y * out_stride + bpp * x + b] = in_data[iy * in_stride + bpp * ix + b]; } } } } } return Error::Ok; } void HeifPixelImage::debug_dump() const { auto channels = get_channel_set(); for (auto c : channels) { int stride = 0; const uint8_t* p = get_plane(c, &stride); for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { printf("%02x ", p[y * stride + x]); } printf("\n"); } } }