Coverage Report

Created: 2026-09-14 07:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/image-items/mask_image.cc
Line
Count
Source
1
/*
2
 * HEIF mask image codec.
3
 *
4
 * Copyright (c) 2023 Dirk Farin <dirk.farin@gmail.com>
5
 * Copyright (c) 2023 Brad Hards <bradh@frogmouth.net>
6
 *
7
 * This file is part of libheif.
8
 *
9
 * libheif is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Lesser General Public License as
11
 * published by the Free Software Foundation, either version 3 of
12
 * the License, or (at your option) any later version.
13
 *
14
 * libheif is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU Lesser General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public License
20
 * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
#include <cstdint>
24
#include <cstring>
25
#include <algorithm>
26
#include <map>
27
#include <cstring>
28
29
#include "libheif/heif.h"
30
#include "logging.h"
31
#include "mask_image.h"
32
#include "image_item.h"
33
#include "security_limits.h"
34
35
36
Error Box_mskC::parse(BitstreamRange& range, const heif_security_limits* limits)
37
129
{
38
129
  parse_full_box_header(range);
39
129
  m_bits_per_pixel = range.read8();
40
129
  return range.get_error();
41
129
}
42
43
std::string Box_mskC::dump(Indent& indent) const
44
0
{
45
0
  std::ostringstream sstr;
46
0
  sstr << Box::dump(indent);
47
0
  sstr << indent << "bits_per_pixel: " << ((int)m_bits_per_pixel) << "\n";
48
0
  return sstr.str();
49
0
}
50
51
Error Box_mskC::write(StreamWriter& writer) const
52
0
{
53
0
  size_t box_start = reserve_box_header_space(writer);
54
0
  writer.write8(m_bits_per_pixel);
55
0
  prepend_header(writer, box_start);
56
0
  return Error::Ok;
57
0
}
58
59
60
Error MaskImageCodec::decode_mask_image(const HeifContext* context,
61
                                        heif_item_id ID,
62
                                        std::shared_ptr<HeifPixelImage>& img,
63
                                        const std::vector<uint8_t>& data)
64
0
{
65
0
  auto image = context->get_image(ID, false);
66
0
  if (!image) {
67
0
    return {heif_error_Invalid_input,
68
0
            heif_suberror_Nonexisting_item_referenced};
69
0
  }
70
71
0
  std::shared_ptr<Box_ispe> ispe = image->get_property<Box_ispe>();
72
0
  std::shared_ptr<Box_mskC> mskC = image->get_property<Box_mskC>();
73
74
0
  if (!ispe || !mskC) {
75
0
    return Error(heif_error_Unsupported_feature,
76
0
                  heif_suberror_Unsupported_data_version,
77
0
                  "Missing required box for mask codec");
78
0
  }
79
80
0
  uint32_t width = ispe->get_width();
81
0
  uint32_t height = ispe->get_height();
82
83
0
  Error error = check_for_valid_image_size(context->get_security_limits(), width, height);
84
0
  if (error) {
85
0
    return error;
86
0
  }
87
88
0
  if ((mskC->get_bits_per_pixel() != 8) && (mskC->get_bits_per_pixel() != 16))
89
0
  {
90
0
    return Error(heif_error_Unsupported_feature,
91
0
                 heif_suberror_Unsupported_data_version,
92
0
                 "Unsupported bit depth for mask item");
93
0
  }
94
95
0
  uint32_t bytes_per_pixel = (mskC->get_bits_per_pixel() + 7) / 8;
96
97
0
  if (data.size() / (static_cast<size_t>(width) * bytes_per_pixel) < height) {
98
0
    return {heif_error_Invalid_input,
99
0
            heif_suberror_Unspecified,
100
0
            "Mask image data is too short"};
101
0
  }
102
103
0
  img = std::make_shared<HeifPixelImage>();
104
0
  img->create(width, height, heif_colorspace_monochrome, heif_chroma_monochrome);
105
0
  auto err = img->add_channel(heif_channel_Y, width, height, mskC->get_bits_per_pixel(),
106
0
                            context->get_security_limits());
107
0
  if (err) {
108
0
    return err;
109
0
  }
110
111
0
  size_t stride;
112
0
  uint8_t* dst = img->get_channel_memory(heif_channel_Y, &stride);
113
0
  if (stride == static_cast<size_t>(width) * bytes_per_pixel) {
114
0
    memcpy(dst, data.data(), static_cast<size_t>(width) * bytes_per_pixel * height);
115
0
  }
116
0
  else
117
0
  {
118
0
    for (size_t i = 0; i < height; i++)
119
0
    {
120
0
      memcpy(dst + i * stride, data.data() + i * width * bytes_per_pixel, width * bytes_per_pixel);
121
0
    }
122
0
  }
123
0
  return Error::Ok;
124
0
}
125
126
127
Result<std::shared_ptr<HeifPixelImage>> ImageItem_mask::decode_compressed_image(const heif_decoding_options& options,
128
                                                                                bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0,
129
                                                                                DecodeTraversalState decode_state) const
130
2
{
131
2
  std::shared_ptr<HeifPixelImage> img;
132
133
2
  std::vector<uint8_t> data;
134
135
  // image data, usually from 'mdat'
136
137
2
  Error error = get_file()->append_data_from_iloc(get_id(), data);
138
2
  if (error) {
139
2
    return error;
140
2
  }
141
142
0
  Error err = MaskImageCodec::decode_mask_image(get_context(),
143
0
                                                get_id(),
144
0
                                                img,
145
0
                                                data);
146
0
  if (err) {
147
0
    return err;
148
0
  }
149
0
  else {
150
0
    return img;
151
0
  }
152
0
}
153
154
155
Result<Encoder::CodedImageData> ImageItem_mask::encode(const std::shared_ptr<HeifPixelImage>& image,
156
                                                       heif_encoder* encoder,
157
                                                       const heif_encoding_options& options,
158
                                                       heif_image_input_class input_class)
159
0
{
160
0
  Encoder::CodedImageData codedImageData;
161
162
0
  if (image->get_colorspace() != heif_colorspace_monochrome)
163
0
  {
164
0
    return Error(heif_error_Unsupported_feature,
165
0
                 heif_suberror_Unsupported_data_version,
166
0
                 "Unsupported colourspace for mask region");
167
0
  }
168
169
0
  if (image->get_bits_per_pixel(heif_channel_Y) != 8)
170
0
  {
171
0
    return Error(heif_error_Unsupported_feature,
172
0
                 heif_suberror_Unsupported_data_version,
173
0
                 "Unsupported bit depth for mask region");
174
0
  }
175
176
  // TODO: we could add an option to lossless-compress this data
177
0
  std::vector<uint8_t> data;
178
0
  size_t src_stride;
179
0
  uint8_t* src_data = image->get_channel_memory(heif_channel_Y, &src_stride);
180
181
0
  uint32_t w = image->get_width();
182
0
  uint32_t h = image->get_height();
183
184
0
  data.resize(w * h);
185
186
0
  if (w == (uint32_t)src_stride) {
187
0
    codedImageData.append(src_data, w*h);
188
0
  }
189
0
  else {
190
0
    for (uint32_t y = 0; y < h; y++) {
191
0
      codedImageData.append(src_data + y * src_stride, w);
192
0
    }
193
0
  }
194
195
0
  std::shared_ptr<Box_mskC> mskC = std::make_shared<Box_mskC>();
196
0
  uint16_t bpp = image->get_bits_per_pixel(heif_channel_Y);
197
0
  if (bpp > 255) {
198
0
    return Error{
199
0
      heif_error_Invalid_input,
200
0
      heif_suberror_Unspecified,
201
0
      "Too many bits per pixel for mask image."
202
0
    };
203
0
  }
204
0
  mskC->set_bits_per_pixel(static_cast<uint8_t>(bpp));
205
0
  codedImageData.properties.push_back(mskC);
206
207
0
  return codedImageData;
208
0
}
209
210
211
int ImageItem_mask::get_luma_bits_per_pixel() const
212
11
{
213
11
  auto mskC = get_property<Box_mskC>();
214
11
  if (!mskC) {
215
11
    return -1;
216
11
  }
217
218
0
  return mskC->get_bits_per_pixel();
219
11
}