1/*
2 * Copyright (c) 2020-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 "components/ui_image_view.h"
17#include "common/image.h"
18#include "common/typed_text.h"
19#include "draw/draw_image.h"
20#include "draw/draw_label.h"
21#include "engines/gfx/gfx_engine_manager.h"
22#include "gfx_utils/file.h"
23#include "gfx_utils/image_info.h"
24#include "gfx_utils/mem_api.h"
25#include "imgdecode/cache_manager.h"
26#if defined(ENABLE_GIF) && (ENABLE_GIF == 1)
27#include "gif_lib.h"
28#endif
29
30namespace OHOS {
31#if defined(ENABLE_GIF) && (ENABLE_GIF == 1)
32class GifImageAnimator : public Animator, public AnimatorCallback {
33public:
34    GifImageAnimator(UIView* view, const char* src)
35        : Animator(this, view, 0, true),
36          gifFileType_(nullptr),
37          imageIndex_(0),
38          delayTime_(0),
39          lastRunTime_(0),
40          deltaTime_(0),
41          gifDataSize_(0),
42          src_(src)
43    {
44    }
45
46    virtual ~GifImageAnimator()
47    {
48        CloseGifFile();
49    }
50
51    void Callback(UIView* view) override;
52
53    void SetGifFileType(GifFileType* gifFileType)
54    {
55        gifFileType_ = gifFileType;
56    }
57
58    uint32_t SetGifFrame(GifFileType* gifFileType, int32_t imageIndex, UIImageView* imageView) const;
59    void DealGifImageData(const GifFileType* gifFileType,
60                          const GifImageDesc* gifImageDesc,
61                          const SavedImage* savedImage,
62                          GraphicsControlBlock gcb,
63                          const ColorMapObject* colorMap) const;
64    void OpenGifFile(const char* src);
65    void CloseGifFile();
66
67private:
68    GifFileType* GetGifFileType()
69    {
70        if (gifFileType_ == nullptr) {
71            OpenGifFile(src_);
72        }
73        return gifFileType_;
74    }
75
76    GifFileType* gifFileType_;
77    int32_t imageIndex_;
78    uint32_t delayTime_;
79    uint32_t lastRunTime_;
80    uint32_t deltaTime_;
81    uint32_t gifDataSize_;
82    uint8_t* gifImageData_ = nullptr;
83    const char* src_;
84};
85
86void GifImageAnimator::OpenGifFile(const char* src)
87{
88    int error = D_GIF_SUCCEEDED;
89    GifFileType* gifFileType = DGifOpenFileName(src, &error);
90    if (error != D_GIF_SUCCEEDED) {
91        return;
92    }
93    DGifSlurp(gifFileType);
94    /* 3 : when change single pixel to byte, the buffer should divided by 8, equal to shift right 3 bits. */
95    uint8_t pixelByteSize = DrawUtils::GetPxSizeByColorMode(ARGB8888) >> 3;
96    gifDataSize_ = gifFileType->SWidth * gifFileType->SHeight * pixelByteSize;
97    gifImageData_ = static_cast<uint8_t*>(UIMalloc(gifDataSize_));
98    if (gifImageData_ == nullptr) {
99        CloseGifFile();
100        return;
101    }
102    SetGifFileType(gifFileType);
103}
104
105void GifImageAnimator::CloseGifFile()
106{
107    GifFileType* gifFileType = GetGifFileType();
108    if (gifFileType != nullptr) {
109        DGifCloseFile(gifFileType, nullptr);
110    }
111    if (gifImageData_ != nullptr) {
112        UIFree(reinterpret_cast<void*>(const_cast<uint8_t*>(gifImageData_)));
113        gifImageData_ = nullptr;
114    }
115}
116
117void GifImageAnimator::Callback(UIView* view)
118{
119    if (view == nullptr) {
120        return;
121    }
122    UIImageView* imageView = static_cast<UIImageView*>(view);
123    uint32_t curTime = GetRunTime();
124    if (curTime != 0) {
125        if (curTime + deltaTime_ - lastRunTime_ >= delayTime_) {
126            deltaTime_ = curTime + deltaTime_ - lastRunTime_ - delayTime_;
127            lastRunTime_ = curTime;
128        } else {
129            return;
130        }
131    }
132    GifFileType* gifFileType = GetGifFileType();
133    if (gifFileType != nullptr) {
134        delayTime_ = SetGifFrame(gifFileType, imageIndex_, imageView);
135        imageIndex_ = (imageIndex_ < gifFileType->ImageCount - 1) ? (imageIndex_ + 1) : 0;
136    }
137}
138
139uint32_t GifImageAnimator::SetGifFrame(GifFileType* gifFileType, int32_t imageIndex, UIImageView* imageView) const
140{
141    SavedImage* savedImage = &(gifFileType->SavedImages[imageIndex]);
142    if (savedImage == nullptr) {
143        return 0;
144    }
145    GifImageDesc* gifImageDesc = &(savedImage->ImageDesc);
146    if (gifImageDesc == nullptr) {
147        return 0;
148    }
149    GraphicsControlBlock gcb;
150    int32_t ret = DGifSavedExtensionToGCB(gifFileType, imageIndex, &gcb);
151    if (ret != GIF_OK) {
152        return 0;
153    }
154    ColorMapObject* colorMap = nullptr;
155    if (gifImageDesc->ColorMap != nullptr) {
156        colorMap = gifImageDesc->ColorMap;
157    } else {
158        colorMap = gifFileType->SColorMap;
159    }
160
161    DealGifImageData(gifFileType, gifImageDesc, savedImage, gcb, colorMap);
162    if (gifImageData_ == nullptr) {
163        return 0;
164    }
165    imageView->gifFrameFlag_ = true;
166    ImageInfo gifFrame;
167    gifFrame.header.width = gifFileType->SWidth;
168    gifFrame.header.height = gifFileType->SHeight;
169    gifFrame.header.colorMode = ARGB8888;
170    gifFrame.dataSize = gifDataSize_;
171    gifFrame.data = gifImageData_;
172    imageView->SetSrc(&gifFrame);
173
174    if (gcb.DelayTime >= 0) {
175        return static_cast<uint32_t>(gcb.DelayTime) * 10; // 10: change hundredths (1/100) of a second to millisecond
176    } else {
177        return 0;
178    }
179}
180
181void GifImageAnimator::DealGifImageData(const GifFileType* gifFileType,
182                                        const GifImageDesc* gifImageDesc,
183                                        const SavedImage* savedImage,
184                                        GraphicsControlBlock gcb,
185                                        const ColorMapObject* colorMap) const
186{
187    if ((gifFileType == nullptr) || (gifImageDesc == nullptr) || (savedImage == nullptr) ||
188        (savedImage->RasterBits == nullptr) || (colorMap == nullptr) || (colorMap->Colors == nullptr)) {
189        return;
190    }
191    uint8_t colorIndex = 0;
192    GifColorType* gifColorType = nullptr;
193    uint32_t index = 0;
194
195    for (int32_t x = 0; x < gifFileType->SHeight; x++) {
196        for (int32_t y = 0; y < gifFileType->SWidth; y++) {
197            bool transparentColor = true;
198            if ((x >= gifImageDesc->Top) && (x < gifImageDesc->Top + gifImageDesc->Height) &&
199                (y >= gifImageDesc->Left) && (y < gifImageDesc->Left + gifImageDesc->Width)) {
200                int32_t loc = (x - gifImageDesc->Top) * gifImageDesc->Width + (y - gifImageDesc->Left);
201                colorIndex = savedImage->RasterBits[loc];
202
203                if ((gcb.DisposalMode != DISPOSE_DO_NOT) || (gcb.TransparentColor == NO_TRANSPARENT_COLOR) ||
204                    (colorIndex != gcb.TransparentColor)) {
205                    transparentColor = false;
206                }
207            }
208            if (transparentColor) {
209                index += 4; // 4: skip color index, keep last frame color
210            } else {
211                gifColorType = &colorMap->Colors[colorIndex];
212                gifImageData_[index++] = gifColorType->Blue;
213                gifImageData_[index++] = gifColorType->Green;
214                gifImageData_[index++] = gifColorType->Red;
215                gifImageData_[index++] = OPA_OPAQUE;
216            }
217        }
218    }
219}
220#endif
221
222UIImageView::UIImageView()
223    : imageWidth_(0),
224      imageHeight_(0),
225      autoEnable_(true),
226      needRefresh_(false),
227      colorFormat_(UNKNOWN),
228      blurLevel_(BlurLevel::LEVEL0),
229      algorithm_(TransformAlgorithm::BILINEAR),
230      reserve_(0)
231{
232    style_ = &(StyleDefault::GetBackgroundTransparentStyle());
233#if defined(ENABLE_GIF) && (ENABLE_GIF == 1)
234    gifImageAnimator_ = nullptr;
235    gifFrameFlag_ = false;
236#endif
237}
238
239UIImageView::~UIImageView()
240{
241#if defined(ENABLE_GIF) && (ENABLE_GIF == 1)
242    RemoveAndStopGifAnimator();
243#endif
244    if (drawTransMap_ != nullptr) {
245        delete drawTransMap_;
246        drawTransMap_ = nullptr;
247    }
248    if (contentMatrix_ != nullptr) {
249        delete contentMatrix_;
250        contentMatrix_ = nullptr;
251    }
252}
253
254void UIImageView::SetResizeMode(ImageResizeMode mode)
255{
256    // when automatic adaptation is enabled only save the mode, no need to update the DrawtransMap
257    if (autoEnable_) {
258        imageResizeMode_ = mode;
259    } else if (imageResizeMode_ != mode) {
260        needRefresh_ = true;
261        ReMeasure();
262        // must update the mode, before calling UpdateDrawTransMap
263        imageResizeMode_ = mode;
264        UpdateDrawTransMap(true);
265    }
266}
267
268void UIImageView::AdjustScaleAndTranslate(Vector3<float>& scale, Vector3<int16_t>& translate,
269    int16_t widgetWidth, int16_t widgetHeight) const
270{
271    // adjust scale
272    float ratio = 1.0f;
273    switch (imageResizeMode_) {
274        case ImageResizeMode::COVER:
275            ratio = MATH_MAX(scale.x_, scale.y_);
276            break;
277        case ImageResizeMode::CONTAIN:
278            ratio = MATH_MIN(scale.x_, scale.y_);
279            break;
280        case ImageResizeMode::CENTER: // ratio is 1.0f
281            break;
282        case ImageResizeMode::SCALE_DOWN:
283            ratio = MATH_MIN(scale.x_, scale.y_);
284            ratio = MATH_MIN(ratio, 1.0f);
285            break;
286        case ImageResizeMode::FILL: // do nothing
287            return;
288        default:
289            break;
290    }
291    if (scale.x_ != ratio) {
292        scale.x_ = ratio;
293        // 0.5: adjust the x-coordinate of the content to the center of widget
294        translate.x_ += (static_cast<float>(widgetWidth) - static_cast<float>(imageWidth_) * ratio) * 0.5f;
295    }
296    if (scale.y_ != ratio) {
297        scale.y_ = ratio;
298        // 0.5: adjust the y-coordinate of the content to the center of widget
299        translate.y_ += (static_cast<float>(widgetHeight) - static_cast<float>(imageHeight_) * ratio) * 0.5f;
300    }
301}
302
303void UIImageView::UpdateContentMatrix()
304{
305    Rect viewRect = GetOrigRect();
306    if (autoEnable_ || (imageResizeMode_ == ImageResizeMode::NONE) ||
307        (imageWidth_ == viewRect.GetWidth() && imageHeight_ == viewRect.GetHeight()) ||
308        imageWidth_ == 0 || imageHeight_ == 0) {
309        if (contentMatrix_ != nullptr) {
310            delete contentMatrix_;
311            contentMatrix_ = nullptr;
312        }
313        return;
314    }
315    if (contentMatrix_ == nullptr) {
316        contentMatrix_ = new Matrix4<float>();
317        if (contentMatrix_ == nullptr) {
318            GRAPHIC_LOGE("can not new contentMatrix");
319            return;
320        }
321    }
322    int16_t widgetWidth = viewRect.GetWidth() - style_->paddingLeft_ - style_->paddingRight_ -
323        style_->borderWidth_ * 2; // 2: excludes the border-left and border-right
324    int16_t widgetHeight = viewRect.GetHeight() - style_->paddingTop_ - style_->paddingBottom_ -
325        style_->borderWidth_ * 2; // 2: excludes the border-top and border-bottom
326
327    float scaleX = static_cast<float>(widgetWidth) / static_cast<float>(imageWidth_);
328    float scaleY = static_cast<float>(widgetHeight) / static_cast<float>(imageHeight_);
329    Vector3<float> scale(scaleX, scaleY, 1.0f);
330    Vector3<int16_t> translate(style_->paddingLeft_ + style_->borderWidth_,
331        style_->paddingTop_ + style_->borderWidth_, 0);
332    AdjustScaleAndTranslate(scale, translate, widgetWidth, widgetHeight);
333
334    auto scaleMatrix = Matrix4<float>::Scale(scale, Vector3<float>(viewRect.GetX(), viewRect.GetY(), 0));
335    auto translateMatrix = Matrix4<float>::Translate(Vector3<float>(translate.x_, translate.y_, 0));
336    *contentMatrix_ = translateMatrix * scaleMatrix;
337}
338
339void UIImageView::UpdateDrawTransMap(bool updateContentMatrix)
340{
341    auto viewRect = GetOrigRect();
342    if (updateContentMatrix || (drawTransMap_ != nullptr &&
343        (drawTransMap_->GetTransMapRect().GetX() != viewRect.GetX() ||
344        drawTransMap_->GetTransMapRect().GetY() != viewRect.GetY()))) {
345        UpdateContentMatrix();
346    }
347    // has no transformation
348    if ((contentMatrix_ == nullptr) && ((transMap_ == nullptr) || transMap_->IsInvalid())) {
349        if (drawTransMap_ != nullptr) {
350            delete drawTransMap_;
351            drawTransMap_ = nullptr;
352        }
353        return;
354    }
355    if (drawTransMap_ == nullptr) {
356        drawTransMap_ = new TransformMap();
357        if (drawTransMap_ == nullptr) {
358            GRAPHIC_LOGE("can not new drawTransMap");
359            return;
360        }
361    }
362    if (contentMatrix_ != nullptr) {
363        drawTransMap_->SetTransMapRect(Rect(viewRect.GetX(), viewRect.GetY(),
364            viewRect.GetX() + imageWidth_ - 1, viewRect.GetY() + imageHeight_ - 1));
365    } else {
366        drawTransMap_->SetTransMapRect(viewRect);
367    }
368    // only contentMatrix
369    if (transMap_ == nullptr || transMap_->IsInvalid()) {
370        if (contentMatrix_ == nullptr) {
371            GRAPHIC_LOGE("Text: UpdateDrawTransMap contentMatrix_ is nullptr");
372            return;
373        }
374        drawTransMap_->SetMatrix(*contentMatrix_);
375        return;
376    }
377    // update the transMap, now the transMap is not nullptr
378    if (!(transMap_->GetTransMapRect() == viewRect)) {
379        transMap_->SetTransMapRect(viewRect);
380    }
381    // only transMap
382    if (contentMatrix_ == nullptr) {
383        *drawTransMap_ = *transMap_;
384        return;
385    }
386    // merge the transMap and content matrix
387    auto rect = transMap_->GetTransMapRect();
388    auto translate = Matrix4<float>::Translate(Vector3<float>(-rect.GetX(), -rect.GetY(), 0));
389    auto matrix = transMap_->GetTransformMatrix() * translate;
390    matrix = matrix * (*contentMatrix_);
391    drawTransMap_->SetMatrix(matrix);
392}
393
394void UIImageView::SetHeight(int16_t height)
395{
396    if (GetHeight() != height) {
397        UIView::SetHeight(height);
398        UpdateDrawTransMap(true);
399    }
400}
401
402void UIImageView::SetWidth(int16_t width)
403{
404    if (GetWidth() != width) {
405        UIView::SetWidth(width);
406        UpdateDrawTransMap(true);
407    }
408}
409
410bool UIImageView::OnPreDraw(Rect& invalidatedArea) const
411{
412    if ((image_.GetSrcType() == IMG_SRC_UNKNOWN)) {
413        return true;
414    }
415
416    if ((colorFormat_ == RGB565) || (colorFormat_ == RGB888)) {
417        if (GetRect().IsContains(invalidatedArea)) {
418            return true;
419        }
420        invalidatedArea.Intersect(invalidatedArea, GetRect());
421    }
422
423    return false;
424}
425
426void UIImageView::OnDraw(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea)
427{
428    OpacityType opa = GetMixOpaScale();
429    BaseGfxEngine* baseGfxEngine = BaseGfxEngine::GetInstance();
430    baseGfxEngine->DrawRect(gfxDstBuffer, GetRect(), invalidatedArea, *style_, opa);
431    if ((imageHeight_ == 0) || (imageWidth_ == 0)) {
432        return;
433    }
434    UpdateDrawTransMap();
435    Rect viewRect = GetContentRect();
436    Rect trunc(invalidatedArea);
437    if (trunc.Intersect(trunc, viewRect)) {
438        uint8_t srcType = image_.GetSrcType();
439        if ((srcType == IMG_SRC_FILE) || (srcType == IMG_SRC_VARIABLE)) {
440            Rect cordsTmp;
441            cordsTmp.SetTop(viewRect.GetY());
442            cordsTmp.SetBottom(viewRect.GetY() + imageHeight_ - 1);
443
444            if ((drawTransMap_ == nullptr) || drawTransMap_->IsInvalid()) {
445                SetCordsTmpRect(gfxDstBuffer, viewRect, trunc, cordsTmp, opa);
446            } else if ((drawTransMap_ != nullptr) && !drawTransMap_->IsInvalid()) {
447                ImageInfo imgInfo;
448                if (srcType == IMG_SRC_FILE) {
449                    CacheEntry entry;
450                    RetCode ret = CacheManager::GetInstance().Open(GetPath(), *style_, entry);
451                    if (ret != RetCode::OK) {
452                        return;
453                    }
454                    imgInfo = entry.GetImageInfo();
455                } else {
456                    imgInfo = *(GetImageInfo());
457                }
458                uint8_t pxSize = DrawUtils::GetPxSizeByColorMode(imgInfo.header.colorMode);
459                TransformDataInfo imageTranDataInfo = {imgInfo.header, imgInfo.data, pxSize,
460                                                       static_cast<BlurLevel>(blurLevel_),
461                                                       static_cast<TransformAlgorithm>(algorithm_)};
462                OpacityType opaScale = DrawUtils::GetMixOpacity(opa, style_->imageOpa_);
463                Matrix4<float> scaleMatrix = drawTransMap_->GetScaleMatrix();
464                int16_t paddingX = style_->paddingLeft_ * scaleMatrix[0][0];
465                int16_t paddingY = style_->paddingTop_ * scaleMatrix[1][1];
466                baseGfxEngine->DrawTransform(gfxDstBuffer, trunc, {paddingX, paddingY}, Color::Black(),
467                                             opaScale, *drawTransMap_, imageTranDataInfo);
468            }
469        }
470    }
471}
472
473void UIImageView::SetCordsTmpRect(BufferInfo& gfxDstBuffer, Rect& viewRect, Rect& trunc,
474                                  Rect& cordsTmp, OpacityType opa)
475{
476    while (cordsTmp.GetTop() <= viewRect.GetBottom()) {
477        cordsTmp.SetLeft(viewRect.GetX());
478        cordsTmp.SetRight(viewRect.GetX() + imageWidth_ - 1);
479        while (cordsTmp.GetLeft() <= viewRect.GetRight()) {
480            image_.DrawImage(gfxDstBuffer, cordsTmp, trunc, *style_, opa);
481            cordsTmp.SetLeft(cordsTmp.GetLeft() + imageWidth_);
482            cordsTmp.SetRight(cordsTmp.GetRight() + imageWidth_);
483        }
484        cordsTmp.SetTop(cordsTmp.GetTop() + imageHeight_);
485        cordsTmp.SetBottom(cordsTmp.GetBottom() + imageHeight_);
486    }
487}
488
489void UIImageView::SetSrc(const char* src)
490{
491#if defined(ENABLE_GIF) && (ENABLE_GIF == 1)
492    if (src == nullptr) {
493        return;
494    }
495    const static uint8_t IMG_BYTES_TO_CHECK = 4; // 4: check 4 bytes of image file
496    char buf[IMG_BYTES_TO_CHECK] = {0};
497    int32_t fd = open(src, O_RDONLY);
498    if (fd < 0) {
499        return;
500    }
501    if (read(fd, buf, IMG_BYTES_TO_CHECK) != IMG_BYTES_TO_CHECK) {
502        close(fd);
503        return;
504    }
505    close(fd);
506    bool updated = false;
507    RemoveAndStopGifAnimator();
508    // 0x47 0x49 0x46: GIF file's header
509    if ((static_cast<uint8_t>(buf[0]) == 0x47) && (static_cast<uint8_t>(buf[1]) == 0x49) &&
510        (static_cast<uint8_t>(buf[2]) == 0x46)) { // 2: array index of GIF file's header
511        if (gifImageAnimator_ == nullptr) {
512            gifImageAnimator_ = new GifImageAnimator(this, src);
513            if (gifImageAnimator_ == nullptr) {
514                GRAPHIC_LOGE("new GifImageAnimator fail");
515                return;
516            }
517        }
518        AddAndStartGifAnimator();
519        updated = true;
520    } else {
521        updated = image_.SetSrc(src);
522    }
523#else
524    bool updated = image_.SetSrc(src);
525#endif
526    if (!updated) {
527        return;
528    }
529    needRefresh_ = true;
530    if (autoEnable_ || (imageResizeMode_ != ImageResizeMode::NONE)) {
531        UIImageView::ReMeasure();
532    }
533    if (imageResizeMode_ != ImageResizeMode::NONE) {
534        UpdateDrawTransMap(true);
535    }
536    Invalidate();
537}
538
539void UIImageView::ReMeasure()
540{
541    if (!needRefresh_) {
542        return;
543    }
544    needRefresh_ = false;
545
546    ImageHeader header = {0};
547    image_.GetHeader(header);
548
549    imageWidth_ = header.width;
550    imageHeight_ = header.height;
551    colorFormat_ = header.colorMode;
552
553    if (autoEnable_) {
554        Invalidate();
555        Resize(imageWidth_, imageHeight_);
556        Invalidate();
557    }
558}
559
560void UIImageView::SetSrc(const ImageInfo* src)
561{
562#if defined(ENABLE_GIF) && (ENABLE_GIF == 1)
563    if (!gifFrameFlag_ && (gifImageAnimator_ != nullptr)) {
564        RemoveAndStopGifAnimator();
565    }
566    gifFrameFlag_ = false;
567#endif
568    bool updated = image_.SetSrc(src);
569    if (!updated) {
570        return;
571    }
572    needRefresh_ = true;
573    if (autoEnable_ || (imageResizeMode_ != ImageResizeMode::NONE)) {
574        UIImageView::ReMeasure();
575    }
576    if (imageResizeMode_ != ImageResizeMode::NONE) {
577        UpdateDrawTransMap(true);
578    }
579    Invalidate();
580}
581
582#if defined(ENABLE_GIF) && (ENABLE_GIF == 1)
583void UIImageView::AddAndStartGifAnimator()
584{
585    if (gifImageAnimator_ != nullptr) {
586        gifImageAnimator_->Start();
587    }
588}
589
590void UIImageView::RemoveAndStopGifAnimator()
591{
592    if (gifImageAnimator_ != nullptr) {
593        gifImageAnimator_->Stop();
594        delete gifImageAnimator_;
595        gifImageAnimator_ = nullptr;
596    }
597}
598#endif
599} // namespace OHOS
600