Coverage Report

Created: 2026-09-02 06:43

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
169
{
38
169
  parse_full_box_header(range);
39
169
  m_bits_per_pixel = range.read8();
40
169
  return range.get_error();
41
169
}
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
268
{
65
268
  auto image = context->get_image(ID, false);
66
268
  if (!image) {
67
0
    return {heif_error_Invalid_input,
68
0
            heif_suberror_Nonexisting_item_referenced};
69
0
  }
70
71
268
  std::shared_ptr<Box_ispe> ispe = image->get_property<Box_ispe>();
72
268
  std::shared_ptr<Box_mskC> mskC = image->get_property<Box_mskC>();
73
74
268
  if (!ispe || !mskC) {
75
41
    return Error(heif_error_Unsupported_feature,
76
41
                  heif_suberror_Unsupported_data_version,
77
41
                  "Missing required box for mask codec");
78
41
  }
79
80
227
  uint32_t width = ispe->get_width();
81
227
  uint32_t height = ispe->get_height();
82
83
227
  Error error = check_for_valid_image_size(context->get_security_limits(), width, height);
84
227
  if (error) {
85
0
    return error;
86
0
  }
87
88
227
  if ((mskC->get_bits_per_pixel() != 8) && (mskC->get_bits_per_pixel() != 16))
89
3
  {
90
3
    return Error(heif_error_Unsupported_feature,
91
3
                 heif_suberror_Unsupported_data_version,
92
3
                 "Unsupported bit depth for mask item");
93
3
  }
94
95
224
  uint32_t bytes_per_pixel = (mskC->get_bits_per_pixel() + 7) / 8;
96
97
224
  if (data.size() / (static_cast<size_t>(width) * bytes_per_pixel) < height) {
98
3
    return {heif_error_Invalid_input,
99
3
            heif_suberror_Unspecified,
100
3
            "Mask image data is too short"};
101
3
  }
102
103
221
  img = std::make_shared<HeifPixelImage>();
104
221
  img->create(width, height, heif_colorspace_monochrome, heif_chroma_monochrome);
105
221
  auto err = img->add_channel(heif_channel_Y, width, height, mskC->get_bits_per_pixel(),
106
221
                            context->get_security_limits());
107
221
  if (err) {
108
0
    return err;
109
0
  }
110
111
221
  size_t stride;
112
221
  uint8_t* dst = img->get_channel_memory(heif_channel_Y, &stride);
113
221
  if (stride == static_cast<size_t>(width) * bytes_per_pixel) {
114
3
    memcpy(dst, data.data(), static_cast<size_t>(width) * bytes_per_pixel * height);
115
3
  }
116
218
  else
117
218
  {
118
4.74k
    for (size_t i = 0; i < height; i++)
119
4.53k
    {
120
4.53k
      memcpy(dst + i * stride, data.data() + i * width * bytes_per_pixel, width * bytes_per_pixel);
121
4.53k
    }
122
218
  }
123
221
  return Error::Ok;
124
221
}
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
326
{
131
326
  std::shared_ptr<HeifPixelImage> img;
132
133
326
  std::vector<uint8_t> data;
134
135
  // image data, usually from 'mdat'
136
137
326
  Error error = get_file()->append_data_from_iloc(get_id(), data);
138
326
  if (error) {
139
58
    return error;
140
58
  }
141
142
268
  Error err = MaskImageCodec::decode_mask_image(get_context(),
143
268
                                                get_id(),
144
268
                                                img,
145
268
                                                data);
146
268
  if (err) {
147
47
    return err;
148
47
  }
149
221
  else {
150
221
    return img;
151
221
  }
152
268
}
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
0
{
213
0
  auto mskC = get_property<Box_mskC>();
214
0
  if (!mskC) {
215
0
    return -1;
216
0
  }
217
218
0
  return mskC->get_bits_per_pixel();
219
0
}