1 /*
2 * Copyright (C) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "webp_decoder.h"
17 #include "media_errors.h"
18 #include "multimedia_templates.h"
19 #include "securec.h"
20
21 namespace OHOS {
22 namespace ImagePlugin {
23 using namespace OHOS::HiviewDFX;
24 using namespace MultimediaPlugin;
25 using namespace Media;
26 using namespace MultiMedia;
27
28 namespace {
29 constexpr HiLogLabel LABEL = { LOG_CORE, LOG_TAG_DOMAIN_ID_PLUGIN, "WebpDecoder" };
30 constexpr int32_t WEBP_IMAGE_NUM = 1;
31 constexpr int32_t EXTERNAL_MEMORY = 1;
32 constexpr size_t DECODE_VP8CHUNK_MIN_SIZE = 4096;
33 } // namespace
34
WebpDecoder()35 WebpDecoder::WebpDecoder()
36 {}
37
~WebpDecoder()38 WebpDecoder::~WebpDecoder()
39 {
40 Reset();
41 }
42
SetSource(InputDataStream & sourceStream)43 void WebpDecoder::SetSource(InputDataStream &sourceStream)
44 {
45 stream_ = &sourceStream;
46 state_ = WebpDecodingState::SOURCE_INITED;
47 }
48
GetImageSize(uint32_t index,PlSize & size)49 uint32_t WebpDecoder::GetImageSize(uint32_t index, PlSize &size)
50 {
51 if (index >= WEBP_IMAGE_NUM) {
52 HiLog::Error(LABEL, "image size:invalid index, index:%{public}u, range:%{public}u.", index, WEBP_IMAGE_NUM);
53 return ERR_IMAGE_INVALID_PARAMETER;
54 }
55 if (state_ < WebpDecodingState::SOURCE_INITED) {
56 HiLog::Error(LABEL, "get image size failed for state %{public}d.", state_);
57 return ERR_MEDIA_INVALID_OPERATION;
58 }
59 if (state_ >= WebpDecodingState::BASE_INFO_PARSED) {
60 size = webpSize_;
61 return SUCCESS;
62 }
63
64 uint32_t ret = DecodeHeader();
65 if (ret != SUCCESS) {
66 HiLog::Debug(LABEL, "decode header error on get image ret:%{public}u.", ret);
67 return ret;
68 }
69 size = webpSize_;
70 return SUCCESS;
71 }
72
SetDecodeOptions(uint32_t index,const PixelDecodeOptions & opts,PlImageInfo & info)73 uint32_t WebpDecoder::SetDecodeOptions(uint32_t index, const PixelDecodeOptions &opts, PlImageInfo &info)
74 {
75 if (index >= WEBP_IMAGE_NUM) {
76 HiLog::Error(LABEL, "set option:invalid index, index:%{public}u, range:%{public}u.", index, WEBP_IMAGE_NUM);
77 return ERR_IMAGE_INVALID_PARAMETER;
78 }
79 if (state_ < WebpDecodingState::SOURCE_INITED) {
80 HiLog::Error(LABEL, "set decode option failed for state %{public}d.", state_);
81 return ERR_MEDIA_INVALID_OPERATION;
82 }
83 if (state_ >= WebpDecodingState::IMAGE_DECODING) {
84 FinishOldDecompress();
85 state_ = WebpDecodingState::SOURCE_INITED;
86 }
87 if (state_ < WebpDecodingState::BASE_INFO_PARSED) {
88 uint32_t ret = DecodeHeader();
89 if (ret != SUCCESS) {
90 HiLog::Error(LABEL, "decode header error on set decode options:%{public}u.", ret);
91 state_ = WebpDecodingState::BASE_INFO_PARSING;
92 return ret;
93 }
94 state_ = WebpDecodingState::BASE_INFO_PARSED;
95 }
96
97 bool hasAlpha = true;
98 if (opts.desiredPixelFormat == PlPixelFormat::RGB_565) {
99 hasAlpha = false;
100 info.alphaType = PlAlphaType::IMAGE_ALPHA_TYPE_OPAQUE;
101 } else {
102 info.alphaType = opts.desireAlphaType;
103 }
104 webpMode_ = GetWebpDecodeMode(opts.desiredPixelFormat,
105 hasAlpha && (opts.desireAlphaType == PlAlphaType::IMAGE_ALPHA_TYPE_PREMUL));
106 info.size = webpSize_;
107 info.pixelFormat = outputFormat_;
108 opts_ = opts;
109
110 state_ = WebpDecodingState::IMAGE_DECODING;
111 return SUCCESS;
112 }
113
Decode(uint32_t index,DecodeContext & context)114 uint32_t WebpDecoder::Decode(uint32_t index, DecodeContext &context)
115 {
116 if (index >= WEBP_IMAGE_NUM) {
117 HiLog::Error(LABEL, "decode:invalid index, index:%{public}u, range:%{public}u.", index, WEBP_IMAGE_NUM);
118 return ERR_IMAGE_INVALID_PARAMETER;
119 }
120 if (state_ < WebpDecodingState::IMAGE_DECODING) {
121 HiLog::Error(LABEL, "set decode option failed for state %{public}d.", state_);
122 return ERR_MEDIA_INVALID_OPERATION;
123 }
124 if (state_ > WebpDecodingState::IMAGE_DECODING) {
125 FinishOldDecompress();
126 uint32_t ret = DecodeHeader();
127 if (ret != SUCCESS) {
128 HiLog::Error(LABEL, "decode header error on set decode options:%{public}u.", ret);
129 state_ = WebpDecodingState::BASE_INFO_PARSING;
130 return ret;
131 }
132 bool hasAlpha = true;
133 if (opts_.desiredPixelFormat == PlPixelFormat::RGB_565) {
134 hasAlpha = false;
135 }
136 webpMode_ =
137 GetWebpDecodeMode(opts_.desiredPixelFormat,
138 hasAlpha && opts_.desireAlphaType == PlAlphaType::IMAGE_ALPHA_TYPE_PREMUL);
139 state_ = WebpDecodingState::IMAGE_DECODING;
140 }
141
142 return DoCommonDecode(context);
143 }
144
PromoteIncrementalDecode(uint32_t index,ProgDecodeContext & context)145 uint32_t WebpDecoder::PromoteIncrementalDecode(uint32_t index, ProgDecodeContext &context)
146 {
147 context.totalProcessProgress = 0;
148 if (index >= WEBP_IMAGE_NUM) {
149 HiLog::Error(LABEL, "incremental:invalid index, index:%{public}u, range:%{public}u.", index, WEBP_IMAGE_NUM);
150 return ERR_IMAGE_INVALID_PARAMETER;
151 }
152
153 if (state_ != WebpDecodingState::IMAGE_DECODING) {
154 HiLog::Error(LABEL, "incremental decode failed for state %{public}d.", state_);
155 return ERR_MEDIA_INVALID_OPERATION;
156 }
157
158 if (!IsDataEnough()) {
159 HiLog::Debug(LABEL, "increment data not enough, need next data.");
160 return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
161 }
162 return DoIncrementalDecode(context);
163 }
164
DecodeHeader()165 uint32_t WebpDecoder::DecodeHeader()
166 {
167 uint32_t ret = ReadIncrementalHead();
168 if (ret != SUCCESS) {
169 if (ret == ERR_IMAGE_SOURCE_DATA_INCOMPLETE) {
170 state_ = WebpDecodingState::BASE_INFO_PARSING;
171 } else {
172 state_ = WebpDecodingState::SOURCE_INITED;
173 HiLog::Error(LABEL, "decode image head, ret:%{public}u.", ret);
174 }
175 return ret;
176 }
177 state_ = WebpDecodingState::BASE_INFO_PARSED;
178 return SUCCESS;
179 }
180
ReadIncrementalHead()181 uint32_t WebpDecoder::ReadIncrementalHead()
182 {
183 size_t stremSize = stream_->GetStreamSize();
184 if (stremSize >= DECODE_VP8CHUNK_MIN_SIZE || stream_->IsStreamCompleted()) {
185 stream_->Seek(0);
186 if (!stream_->Read(stream_->GetStreamSize(), dataBuffer_)) {
187 HiLog::Error(LABEL, "read data fail.");
188 return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
189 }
190 if (dataBuffer_.inputStreamBuffer == nullptr || dataBuffer_.dataSize == 0) {
191 HiLog::Error(LABEL, "inputStreamBuffer is null or data size is %{public}u.", dataBuffer_.dataSize);
192 return ERR_IMAGE_GET_DATA_ABNORMAL;
193 }
194
195 int32_t width = 0;
196 int32_t height = 0;
197 int32_t ret = WebPGetInfo(dataBuffer_.inputStreamBuffer, dataBuffer_.bufferSize, &width, &height);
198 if (ret == 0 || (width == 0 && height == 0)) {
199 // may be incomplete data
200 HiLog::Error(LABEL, "get width and height fail.");
201 return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
202 }
203
204 if (width < 0 || height < 0) {
205 HiLog::Error(LABEL, "width and height invalid, width:%{public}d, height:%{public}d.", width, height);
206 return ERR_IMAGE_INVALID_PARAMETER;
207 }
208 webpSize_.width = static_cast<uint32_t>(width);
209 webpSize_.height = static_cast<uint32_t>(height);
210 incrementSize_ = stremSize;
211 lastDecodeSize_ = stremSize;
212 return SUCCESS;
213 }
214
215 return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
216 }
217
IsDataEnough()218 bool WebpDecoder::IsDataEnough()
219 {
220 size_t streamSize = stream_->GetStreamSize();
221 if (incrementSize_ < DECODE_VP8CHUNK_MIN_SIZE && !stream_->IsStreamCompleted()) {
222 incrementSize_ += streamSize - lastDecodeSize_;
223 lastDecodeSize_ = streamSize;
224 return false;
225 }
226 incrementSize_ = streamSize - lastDecodeSize_;
227 lastDecodeSize_ = streamSize;
228 return true;
229 }
230
GetWebpDecodeMode(const PlPixelFormat & pixelFormat,bool premul)231 WEBP_CSP_MODE WebpDecoder::GetWebpDecodeMode(const PlPixelFormat &pixelFormat, bool premul)
232 {
233 WEBP_CSP_MODE webpMode = MODE_RGBA;
234 outputFormat_ = pixelFormat;
235 switch (pixelFormat) {
236 case PlPixelFormat::BGRA_8888:
237 webpMode = premul ? MODE_bgrA : MODE_BGRA;
238 break;
239 case PlPixelFormat::RGBA_8888:
240 webpMode = premul ? MODE_rgbA : MODE_RGBA;
241 break;
242 case PlPixelFormat::RGB_565:
243 bytesPerPixel_ = 2; // RGB_565 2 bytes each pixel
244 webpMode = MODE_RGB_565;
245 break;
246 case PlPixelFormat::UNKNOWN:
247 default:
248 outputFormat_ = PlPixelFormat::RGBA_8888;
249 webpMode = premul ? MODE_rgbA : MODE_RGBA;
250 break;
251 }
252 return webpMode;
253 }
254
FinishOldDecompress()255 void WebpDecoder::FinishOldDecompress()
256 {
257 if (state_ < WebpDecodingState::IMAGE_DECODING) {
258 return;
259 }
260 Reset();
261 }
262
DoCommonDecode(DecodeContext & context)263 uint32_t WebpDecoder::DoCommonDecode(DecodeContext &context)
264 {
265 WebPDecoderConfig config;
266 if (!PreDecodeProc(context, config, false)) {
267 HiLog::Error(LABEL, "prepare common decode failed.");
268 state_ = WebpDecodingState::IMAGE_ERROR;
269 return ERR_IMAGE_MALLOC_ABNORMAL;
270 }
271
272 TAutoCallProc<WebPDecBuffer, WebPFreeDecBuffer> webpOutput(&config.output);
273 TAutoCallProc<WebPIDecoder, WebPIDelete> idec(WebPINewDecoder(&config.output));
274 if (idec == nullptr) {
275 HiLog::Error(LABEL, "common decode:idec is null.");
276 state_ = WebpDecodingState::IMAGE_ERROR;
277 return ERR_IMAGE_DECODE_FAILED;
278 }
279
280 VP8StatusCode status = WebPIUpdate(idec, dataBuffer_.inputStreamBuffer, static_cast<size_t>(dataBuffer_.dataSize));
281 if (status == VP8_STATUS_OK) {
282 state_ = WebpDecodingState::IMAGE_DECODED;
283 return SUCCESS;
284 }
285 if (status == VP8_STATUS_SUSPENDED && opts_.allowPartialImage) {
286 state_ = WebpDecodingState::IMAGE_PARTIAL;
287 context.ifPartialOutput = true;
288 HiLog::Error(LABEL, "this is partial image data to decode.");
289 return SUCCESS;
290 }
291
292 HiLog::Error(LABEL, "decode image data failed, status:%{public}d.", status);
293 state_ = WebpDecodingState::IMAGE_ERROR;
294 return ERR_IMAGE_DECODE_FAILED;
295 }
296
DoIncrementalDecode(ProgDecodeContext & context)297 uint32_t WebpDecoder::DoIncrementalDecode(ProgDecodeContext &context)
298 {
299 WebPDecoderConfig config;
300 if (!PreDecodeProc(context.decodeContext, config, true)) {
301 HiLog::Error(LABEL, "prepare increment decode failed.");
302 return ERR_IMAGE_MALLOC_ABNORMAL;
303 }
304
305 TAutoCallProc<WebPDecBuffer, WebPFreeDecBuffer> webpOutput(&config.output);
306 TAutoCallProc<WebPIDecoder, WebPIDelete> idec(WebPINewDecoder(&config.output));
307 if (idec == nullptr) {
308 HiLog::Error(LABEL, "incremental code:idec is null.");
309 return ERR_IMAGE_DECODE_FAILED;
310 }
311
312 dataBuffer_ = { nullptr, 0, 0 };
313 stream_->Seek(0);
314 if (!stream_->Read(stream_->GetStreamSize(), dataBuffer_)) {
315 HiLog::Error(LABEL, "incremental:read data failed.");
316 return ERR_IMAGE_DECODE_FAILED;
317 }
318 if (dataBuffer_.inputStreamBuffer == nullptr || dataBuffer_.dataSize == 0) {
319 HiLog::Error(LABEL, "incremental:data is null.");
320 return ERR_IMAGE_DECODE_FAILED;
321 }
322
323 VP8StatusCode status = WebPIUpdate(idec, dataBuffer_.inputStreamBuffer, static_cast<size_t>(dataBuffer_.dataSize));
324 if (status != VP8_STATUS_OK && status != VP8_STATUS_SUSPENDED) {
325 HiLog::Error(LABEL, "incremental:webp status exception,status:%{public}d.", status);
326 return ERR_IMAGE_DECODE_FAILED;
327 }
328 if (status == VP8_STATUS_SUSPENDED) {
329 int32_t curHeight = 0;
330 if (WebPIDecGetRGB(idec, &curHeight, nullptr, nullptr, nullptr) == nullptr) {
331 HiLog::Debug(LABEL, "refresh image failed, current height:%{public}d.", curHeight);
332 }
333 if (curHeight > 0 && webpSize_.height != 0) {
334 context.totalProcessProgress =
335 static_cast<uint32_t>(curHeight) * ProgDecodeContext::FULL_PROGRESS / webpSize_.height;
336 }
337 return ERR_IMAGE_SOURCE_DATA_INCOMPLETE;
338 }
339 if (status == VP8_STATUS_OK) {
340 context.totalProcessProgress = context.FULL_PROGRESS;
341 state_ = WebpDecodingState::IMAGE_DECODED;
342 }
343 return SUCCESS;
344 }
345
InitWebpOutput(const DecodeContext & context,WebPDecBuffer & output)346 void WebpDecoder::InitWebpOutput(const DecodeContext &context, WebPDecBuffer &output)
347 {
348 output.is_external_memory = EXTERNAL_MEMORY; // external allocated space
349 output.u.RGBA.rgba = static_cast<uint8_t *>(context.pixelsBuffer.buffer);
350 output.u.RGBA.stride = webpSize_.width * bytesPerPixel_;
351 output.u.RGBA.size = context.pixelsBuffer.bufferSize;
352 output.colorspace = webpMode_;
353 }
354
PreDecodeProc(DecodeContext & context,WebPDecoderConfig & config,bool isIncremental)355 bool WebpDecoder::PreDecodeProc(DecodeContext &context, WebPDecoderConfig &config, bool isIncremental)
356 {
357 if (WebPInitDecoderConfig(&config) == 0) {
358 HiLog::Error(LABEL, "init config failed.");
359 return false;
360 }
361 if (!AllocHeapBuffer(context, isIncremental)) {
362 HiLog::Error(LABEL, "get pixels memory failed.");
363 return false;
364 }
365
366 InitWebpOutput(context, config.output);
367 return true;
368 }
369
Reset()370 void WebpDecoder::Reset()
371 {
372 stream_->Seek(0);
373 dataBuffer_ = { nullptr, 0, 0 };
374 webpSize_ = { 0, 0 };
375 }
376
AllocHeapBuffer(DecodeContext & context,bool isIncremental)377 bool WebpDecoder::AllocHeapBuffer(DecodeContext &context, bool isIncremental)
378 {
379 if (isIncremental) {
380 if (context.pixelsBuffer.buffer != nullptr && context.allocatorType == AllocatorType::HEAP_ALLOC) {
381 free(context.pixelsBuffer.buffer);
382 context.pixelsBuffer.buffer = nullptr;
383 }
384 }
385
386 if (context.pixelsBuffer.buffer == nullptr) {
387 uint64_t byteCount = static_cast<uint64_t>(webpSize_.width * webpSize_.height * bytesPerPixel_);
388 if (context.allocatorType == Media::AllocatorType::SHARE_MEM_ALLOC) {
389 #ifndef _WIN32
390 int fd = AshmemCreate("WEBP RawData", byteCount);
391 if (fd < 0) {
392 return false;
393 }
394 int result = AshmemSetProt(fd, PROT_READ | PROT_WRITE);
395 if (result < 0) {
396 ::close(fd);
397 return false;
398 }
399 void* ptr = ::mmap(nullptr, byteCount, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
400 if (ptr == MAP_FAILED) {
401 ::close(fd);
402 return false;
403 }
404 context.pixelsBuffer.buffer = ptr;
405 void *fdBuffer = new int32_t();
406 if (fdBuffer == nullptr) {
407 HiLog::Error(LABEL, "malloc fdBuffer fail");
408 ::munmap(ptr, byteCount);
409 ::close(fd);
410 context.pixelsBuffer.buffer = nullptr;
411 return false;
412 }
413 *static_cast<int32_t *>(fdBuffer) = fd;
414 context.pixelsBuffer.context = fdBuffer;
415 context.pixelsBuffer.bufferSize = byteCount;
416 context.allocatorType = AllocatorType::SHARE_MEM_ALLOC;
417 context.freeFunc = nullptr;
418 #endif
419 } else {
420 void *outputBuffer = malloc(byteCount);
421 if (outputBuffer == nullptr) {
422 HiLog::Error(LABEL, "alloc output buffer size:[%{public}llu] error.",
423 static_cast<unsigned long long>(byteCount));
424 return false;
425 }
426 #ifdef _WIN32
427 errno_t backRet = memset_s(outputBuffer, 0, byteCount);
428 if (backRet != EOK) {
429 HiLog::Error(LABEL, "memset buffer failed.", backRet);
430 free(outputBuffer);
431 outputBuffer = nullptr;
432 return false;
433 }
434 #else
435 if (memset_s(outputBuffer, byteCount, 0, byteCount) != EOK) {
436 HiLog::Error(LABEL, "memset buffer failed.");
437 free(outputBuffer);
438 outputBuffer = nullptr;
439 return false;
440 }
441 #endif
442 context.pixelsBuffer.buffer = outputBuffer;
443 context.pixelsBuffer.bufferSize = byteCount;
444 context.pixelsBuffer.context = nullptr;
445 context.allocatorType = AllocatorType::HEAP_ALLOC;
446 context.freeFunc = nullptr;
447 }
448 }
449 return true;
450 }
451 } // namespace ImagePlugin
452 } // namespace OHOS
453