libheif/region.cc (437 lines of code) (raw):

/* * HEIF codec. * Copyright (c) 2023 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 "region.h" #include "error.h" #include "file.h" #include "box.h" #include "libheif/heif_regions.h" #include <algorithm> #include <utility> Error RegionItem::parse(const std::vector<uint8_t>& data) { if (data.size() < 8) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, "Less than 8 bytes of data"); } uint8_t version = data[0]; (void) version; // version is unused uint8_t flags = data[1]; int field_size = ((flags & 1) ? 32 : 16); unsigned int dataOffset; if (field_size == 32) { if (data.size() < 12) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, "Region data incomplete"); } reference_width = ((data[2] << 24) | (data[3] << 16) | (data[4] << 8) | (data[5])); reference_height = ((data[6] << 24) | (data[7] << 16) | (data[8] << 8) | (data[9])); dataOffset = 10; } else { reference_width = ((data[2] << 8) | (data[3])); reference_height = ((data[4] << 8) | (data[5])); dataOffset = 6; } uint8_t region_count = data[dataOffset]; dataOffset += 1; for (int i = 0; i < region_count; i++) { if (data.size() <= dataOffset) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, "Region data incomplete"); } uint8_t geometry_type = data[dataOffset]; dataOffset += 1; std::shared_ptr<RegionGeometry> region; if (geometry_type == heif_region_type_point) { region = std::make_shared<RegionGeometry_Point>(); } else if (geometry_type == heif_region_type_rectangle) { region = std::make_shared<RegionGeometry_Rectangle>(); } else if (geometry_type == heif_region_type_ellipse) { region = std::make_shared<RegionGeometry_Ellipse>(); } else if (geometry_type == heif_region_type_polygon) { auto polygon = std::make_shared<RegionGeometry_Polygon>(); polygon->closed = true; region = polygon; } else if (geometry_type == heif_region_type_referenced_mask) { region = std::make_shared<RegionGeometry_ReferencedMask>(); } else if (geometry_type == heif_region_type_inline_mask) { region = std::make_shared<RegionGeometry_InlineMask>(); } else if (geometry_type == heif_region_type_polyline) { auto polygon = std::make_shared<RegionGeometry_Polygon>(); polygon->closed = false; region = polygon; } else { // // TODO: this isn't going to work - we can only exit here. // std::cout << "ignoring unsupported region geometry type: " // << (int)geometry_type << std::endl; continue; } Error error = region->parse(data, field_size, &dataOffset); if (error) { return error; } mRegions.push_back(region); } return Error::Ok; } Error RegionItem::encode(std::vector<uint8_t>& result) const { StreamWriter writer; writer.write8(0); // --- compute required field size int field_size_bytes = 2; if (reference_width <= 0xFFFF && reference_height <= 0xFFFF) { field_size_bytes = 4; } if (field_size_bytes != 4) { for (auto& region : mRegions) { if (region->encode_needs_32bit()) { field_size_bytes = 4; break; } } } // --- write flags uint8_t flags = 0; if (field_size_bytes == 4) { flags |= 1; } writer.write8(flags); // --- write reference size writer.write(field_size_bytes, reference_width); writer.write(field_size_bytes, reference_height); // --- write regions if (mRegions.size() >= 256) { return Error(heif_error_Encoding_error, heif_suberror_Too_many_regions); } writer.write8((uint8_t) mRegions.size()); for (auto& region : mRegions) { region->encode(writer, field_size_bytes); } result = writer.get_data(); return Error::Ok; } uint32_t RegionGeometry::parse_unsigned(const std::vector<uint8_t>& data, int field_size, unsigned int* dataOffset) { uint32_t x; if (field_size == 32) { x = ((data[*dataOffset] << 24) | (data[*dataOffset + 1] << 16) | (data[*dataOffset + 2] << 8) | (data[*dataOffset + 3])); *dataOffset = *dataOffset + 4; } else { x = ((data[*dataOffset] << 8) | (data[*dataOffset + 1])); *dataOffset = *dataOffset + 2; } return x; } int32_t RegionGeometry::parse_signed(const std::vector<uint8_t>& data, int field_size, unsigned int* dataOffset) { if (field_size == 32) { return (int32_t)parse_unsigned(data, field_size, dataOffset); } else { return (int16_t)parse_unsigned(data, field_size, dataOffset); } } Error RegionGeometry_Point::parse(const std::vector<uint8_t>& data, int field_size, unsigned int* dataOffset) { unsigned int bytesRequired = (field_size / 8) * 2; if (data.size() - *dataOffset < bytesRequired) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, "Insufficient data remaining for point region"); } x = parse_signed(data, field_size, dataOffset); y = parse_signed(data, field_size, dataOffset); return Error::Ok; } static bool exceeds_s16(int32_t v) { return (v > 32767 || v < -32768); } static bool exceeds_u16(uint32_t v) { return v > 0xFFFF; } bool RegionGeometry_Point::encode_needs_32bit() const { return exceeds_s16(x) || exceeds_s16(y); } void RegionGeometry_Point::encode(StreamWriter& writer, int field_size_bytes) const { writer.write8(heif_region_type_point); writer.write(field_size_bytes, x); writer.write(field_size_bytes, y); } Error RegionGeometry_Rectangle::parse(const std::vector<uint8_t>& data, int field_size, unsigned int* dataOffset) { unsigned int bytesRequired = (field_size / 8) * 4; if (data.size() - *dataOffset < bytesRequired) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, "Insufficient data remaining for rectangle region"); } x = parse_signed(data, field_size, dataOffset); y = parse_signed(data, field_size, dataOffset); width = parse_unsigned(data, field_size, dataOffset); height = parse_unsigned(data, field_size, dataOffset); return Error::Ok; } bool RegionGeometry_Rectangle::encode_needs_32bit() const { return exceeds_s16(x) || exceeds_s16(y) || exceeds_u16(width) || exceeds_u16(height); } void RegionGeometry_Rectangle::encode(StreamWriter& writer, int field_size_bytes) const { writer.write8(heif_region_type_rectangle); writer.write(field_size_bytes, x); writer.write(field_size_bytes, y); writer.write(field_size_bytes, width); writer.write(field_size_bytes, height); } Error RegionGeometry_Ellipse::parse(const std::vector<uint8_t>& data, int field_size, unsigned int* dataOffset) { unsigned int bytesRequired = (field_size / 8) * 4; if (data.size() - *dataOffset < bytesRequired) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, "Insufficient data remaining for ellipse region"); } x = parse_signed(data, field_size, dataOffset); y = parse_signed(data, field_size, dataOffset); radius_x = parse_unsigned(data, field_size, dataOffset); radius_y = parse_unsigned(data, field_size, dataOffset); return Error::Ok; } bool RegionGeometry_Ellipse::encode_needs_32bit() const { return exceeds_s16(x) || exceeds_s16(y) || exceeds_u16(radius_x) || exceeds_u16(radius_y); } void RegionGeometry_Ellipse::encode(StreamWriter& writer, int field_size_bytes) const { writer.write8(heif_region_type_ellipse); writer.write(field_size_bytes, x); writer.write(field_size_bytes, y); writer.write(field_size_bytes, radius_x); writer.write(field_size_bytes, radius_y); } Error RegionGeometry_Polygon::parse(const std::vector<uint8_t>& data, int field_size, unsigned int* dataOffset) { uint32_t bytesRequired1 = (field_size / 8) * 1; if (data.size() - *dataOffset < bytesRequired1) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, "Insufficient data remaining for polygon"); } // Note: we need to do the calculation in uint64_t because numPoints may be any 32-bit number // and it is multiplied by (at most) 8. uint32_t numPoints = parse_unsigned(data, field_size, dataOffset); uint64_t bytesRequired2 = (field_size / 8) * uint64_t(numPoints) * 2; if (data.size() - *dataOffset < bytesRequired2) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, "Insufficient data remaining for polygon"); } for (uint32_t i = 0; i < numPoints; i++) { Point p; p.x = parse_signed(data, field_size, dataOffset); p.y = parse_signed(data, field_size, dataOffset); points.push_back(p); } return Error::Ok; } Error RegionGeometry_ReferencedMask::parse(const std::vector<uint8_t>& data, int field_size, unsigned int* dataOffset) { unsigned int bytesRequired = (field_size / 8) * 4; if (data.size() - *dataOffset < bytesRequired) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, "Insufficient data remaining for referenced mask region"); } x = parse_signed(data, field_size, dataOffset); y = parse_signed(data, field_size, dataOffset); width = parse_unsigned(data, field_size, dataOffset); height = parse_unsigned(data, field_size, dataOffset); return Error::Ok; } void RegionGeometry_ReferencedMask::encode(StreamWriter& writer, int field_size_bytes) const { writer.write8(heif_region_type_referenced_mask); writer.write(field_size_bytes, x); writer.write(field_size_bytes, y); writer.write(field_size_bytes, width); writer.write(field_size_bytes, height); } bool RegionGeometry_Polygon::encode_needs_32bit() const { if (exceeds_u16((uint32_t)points.size())) { return true; } for (auto& p : points) { if (exceeds_s16(p.x) || exceeds_s16(p.y)) { return true; } } return false; } void RegionGeometry_Polygon::encode(StreamWriter& writer, int field_size_bytes) const { writer.write8(closed ? heif_region_type_polygon : heif_region_type_polyline); writer.write(field_size_bytes, points.size()); for (auto& p : points) { writer.write(field_size_bytes, p.x); writer.write(field_size_bytes, p.y); } } Error RegionGeometry_InlineMask::parse(const std::vector<uint8_t>& data, int field_size, unsigned int* dataOffset) { unsigned int bytesRequired = (field_size / 8) * 4 + 1; if (data.size() - *dataOffset < bytesRequired) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, "Insufficient data remaining for inline mask region"); } x = parse_signed(data, field_size, dataOffset); y = parse_signed(data, field_size, dataOffset); width = parse_unsigned(data, field_size, dataOffset); height = parse_unsigned(data, field_size, dataOffset); uint8_t mask_coding_method = data[*dataOffset]; *dataOffset = *dataOffset + 1; if (mask_coding_method != 0) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, "Deflate compressed inline mask is not yet supported"); } unsigned int additionalBytesRequired = width * height / 8; if (data.size() - *dataOffset < additionalBytesRequired) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, "Insufficient data remaining for inline mask region data[]"); } mask_data.resize(additionalBytesRequired); std::copy(data.begin() + *dataOffset, data.begin() + *dataOffset + additionalBytesRequired, mask_data.begin()); return Error::Ok; } void RegionGeometry_InlineMask::encode(StreamWriter& writer, int field_size_bytes) const { writer.write8(heif_region_type_inline_mask); writer.write(field_size_bytes, x); writer.write(field_size_bytes, y); writer.write(field_size_bytes, width); writer.write(field_size_bytes, height); writer.write8(0); // coding method // if using some other coding method, there are parameters to write out here. writer.write(mask_data); } RegionCoordinateTransform RegionCoordinateTransform::create(std::shared_ptr<HeifFile> file, heif_item_id item_id, int reference_width, int reference_height) { std::vector<std::shared_ptr<Box>> properties; Error err = file->get_properties(item_id, properties); if (err) { return {}; } int image_width = 0, image_height = 0; for (auto& property : properties) { if (property->get_short_type() == fourcc("ispe")) { auto ispe = std::dynamic_pointer_cast<Box_ispe>(property); image_width = ispe->get_width(); image_height = ispe->get_height(); break; } } if (image_width == 0 || image_height == 0) { return {}; } RegionCoordinateTransform transform; transform.a = image_width / (double) reference_width; transform.d = image_height / (double) reference_height; for (auto& property : properties) { switch (property->get_short_type()) { case fourcc("imir"): { auto imir = std::dynamic_pointer_cast<Box_imir>(property); if (imir->get_mirror_direction() == heif_transform_mirror_direction_horizontal) { transform.a = -transform.a; transform.b = -transform.b; transform.tx = image_width - 1 - transform.tx; } else { transform.c = -transform.c; transform.d = -transform.d; transform.ty = image_height - 1 - transform.ty; } break; } case fourcc("irot"): { auto irot = std::dynamic_pointer_cast<Box_irot>(property); RegionCoordinateTransform tmp; switch (irot->get_rotation()) { case 90: tmp.a = transform.c; tmp.b = transform.d; tmp.c = -transform.a; tmp.d = -transform.b; tmp.tx = transform.ty; tmp.ty = -transform.tx + image_width - 1; transform = tmp; std::swap(image_width, image_height); break; case 180: transform.a = -transform.a; transform.b = -transform.b; transform.tx = image_width - 1 - transform.tx; transform.c = -transform.c; transform.d = -transform.d; transform.ty = image_height - 1 - transform.ty; break; case 270: tmp.a = -transform.c; tmp.b = -transform.d; tmp.c = transform.a; tmp.d = transform.b; tmp.tx = -transform.ty + image_height - 1; tmp.ty = transform.tx; transform = tmp; std::swap(image_width, image_height); break; default: break; } break; } case fourcc("clap"): { auto clap = std::dynamic_pointer_cast<Box_clap>(property); int left = clap->left_rounded(image_width); int top = clap->top_rounded(image_height); transform.tx -= left; transform.ty -= top; image_width = clap->get_width_rounded(); image_height = clap->get_height_rounded(); break; } default: break; } } return transform; } RegionCoordinateTransform::Point RegionCoordinateTransform::transform_point(Point p) { Point newp; newp.x = p.x * a + p.x * b + tx; newp.y = p.x * c + p.y * d + ty; return newp; } RegionCoordinateTransform::Extent RegionCoordinateTransform::transform_extent(Extent e) { Extent newe; newe.x = e.x * a + e.y * b; newe.y = e.x * c + e.y * d; return newe; }