1/*
2 * Copyright (C) 2011 Apple Inc.  All rights reserved.
3 * Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "CSSCrossfadeValue.h"
29
30#include "AnimationUtilities.h"
31#include "CSSImageValue.h"
32#include "CachedImage.h"
33#include "CachedResourceLoader.h"
34#include "CrossfadeGeneratedImage.h"
35#include "ImageBuffer.h"
36#include "RenderElement.h"
37#include "StyleCachedImage.h"
38#include "StyleGeneratedImage.h"
39#include <wtf/text/StringBuilder.h>
40
41namespace WebCore {
42
43static inline double blendFunc(double from, double to, double progress)
44{
45    return blend(from, to, progress);
46}
47
48static bool subimageKnownToBeOpaque(CSSValue& value, const RenderElement* renderer)
49{
50    if (value.isImageValue())
51        return toCSSImageValue(value).knownToBeOpaque(renderer);
52
53    if (value.isImageGeneratorValue())
54        return toCSSImageGeneratorValue(value).knownToBeOpaque(renderer);
55
56    ASSERT_NOT_REACHED();
57
58    return false;
59}
60
61CSSCrossfadeValue::~CSSCrossfadeValue()
62{
63    if (m_cachedFromImage)
64        m_cachedFromImage->removeClient(&m_crossfadeSubimageObserver);
65    if (m_cachedToImage)
66        m_cachedToImage->removeClient(&m_crossfadeSubimageObserver);
67}
68
69String CSSCrossfadeValue::customCSSText() const
70{
71    StringBuilder result;
72    result.appendLiteral("-webkit-cross-fade(");
73    result.append(m_fromValue->cssText());
74    result.appendLiteral(", ");
75    result.append(m_toValue->cssText());
76    result.appendLiteral(", ");
77    result.append(m_percentageValue->cssText());
78    result.append(')');
79    return result.toString();
80}
81
82FloatSize CSSCrossfadeValue::fixedSize(const RenderElement* renderer)
83{
84    float percentage = m_percentageValue->getFloatValue();
85    float inversePercentage = 1 - percentage;
86
87    CachedResourceLoader* cachedResourceLoader = renderer->document().cachedResourceLoader();
88    CachedImage* cachedFromImage = cachedImageForCSSValue(m_fromValue.get(), cachedResourceLoader);
89    CachedImage* cachedToImage = cachedImageForCSSValue(m_toValue.get(), cachedResourceLoader);
90
91    if (!cachedFromImage || !cachedToImage)
92        return FloatSize();
93
94    FloatSize fromImageSize = cachedFromImage->imageForRenderer(renderer)->size();
95    FloatSize toImageSize = cachedToImage->imageForRenderer(renderer)->size();
96
97    // Rounding issues can cause transitions between images of equal size to return
98    // a different fixed size; avoid performing the interpolation if the images are the same size.
99    if (fromImageSize == toImageSize)
100        return fromImageSize;
101
102    return FloatSize(fromImageSize.width() * inversePercentage + toImageSize.width() * percentage,
103        fromImageSize.height() * inversePercentage + toImageSize.height() * percentage);
104}
105
106bool CSSCrossfadeValue::isPending() const
107{
108    return CSSImageGeneratorValue::subimageIsPending(m_fromValue.get())
109        || CSSImageGeneratorValue::subimageIsPending(m_toValue.get());
110}
111
112bool CSSCrossfadeValue::knownToBeOpaque(const RenderElement* renderer) const
113{
114    return subimageKnownToBeOpaque(*m_fromValue, renderer) && subimageKnownToBeOpaque(*m_toValue, renderer);
115}
116
117void CSSCrossfadeValue::loadSubimages(CachedResourceLoader* cachedResourceLoader)
118{
119    CachedResourceHandle<CachedImage> oldCachedFromImage = m_cachedFromImage;
120    CachedResourceHandle<CachedImage> oldCachedToImage = m_cachedToImage;
121
122    m_cachedFromImage = CSSImageGeneratorValue::cachedImageForCSSValue(m_fromValue.get(), cachedResourceLoader);
123    m_cachedToImage = CSSImageGeneratorValue::cachedImageForCSSValue(m_toValue.get(), cachedResourceLoader);
124
125    if (m_cachedFromImage != oldCachedFromImage) {
126        if (oldCachedFromImage)
127            oldCachedFromImage->removeClient(&m_crossfadeSubimageObserver);
128        if (m_cachedFromImage)
129            m_cachedFromImage->addClient(&m_crossfadeSubimageObserver);
130    }
131
132    if (m_cachedToImage != oldCachedToImage) {
133        if (oldCachedToImage)
134            oldCachedToImage->removeClient(&m_crossfadeSubimageObserver);
135        if (m_cachedToImage)
136            m_cachedToImage->addClient(&m_crossfadeSubimageObserver);
137    }
138
139    m_crossfadeSubimageObserver.setReady(true);
140}
141
142PassRefPtr<Image> CSSCrossfadeValue::image(RenderElement* renderer, const FloatSize& size)
143{
144    if (size.isEmpty())
145        return 0;
146
147    CachedResourceLoader* cachedResourceLoader = renderer->document().cachedResourceLoader();
148    CachedImage* cachedFromImage = cachedImageForCSSValue(m_fromValue.get(), cachedResourceLoader);
149    CachedImage* cachedToImage = cachedImageForCSSValue(m_toValue.get(), cachedResourceLoader);
150
151    if (!cachedFromImage || !cachedToImage)
152        return Image::nullImage();
153
154    Image* fromImage = cachedFromImage->imageForRenderer(renderer);
155    Image* toImage = cachedToImage->imageForRenderer(renderer);
156
157    if (!fromImage || !toImage)
158        return Image::nullImage();
159
160    m_generatedImage = CrossfadeGeneratedImage::create(fromImage, toImage, m_percentageValue->getFloatValue(), fixedSize(renderer), size);
161
162    return m_generatedImage.release();
163}
164
165void CSSCrossfadeValue::crossfadeChanged(const IntRect&)
166{
167    for (auto it = clients().begin(), end = clients().end(); it != end; ++it)
168        it->key->imageChanged(static_cast<WrappedImagePtr>(this));
169}
170
171void CSSCrossfadeValue::CrossfadeSubimageObserverProxy::imageChanged(CachedImage*, const IntRect* rect)
172{
173    if (m_ready)
174        m_ownerValue->crossfadeChanged(*rect);
175}
176
177bool CSSCrossfadeValue::hasFailedOrCanceledSubresources() const
178{
179    if (m_cachedFromImage && m_cachedFromImage->loadFailedOrCanceled())
180        return true;
181    if (m_cachedToImage && m_cachedToImage->loadFailedOrCanceled())
182        return true;
183    return false;
184}
185
186PassRefPtr<CSSCrossfadeValue> CSSCrossfadeValue::blend(const CSSCrossfadeValue& from, double progress) const
187{
188    ASSERT(equalInputImages(from));
189    RefPtr<StyleCachedImage> toStyledImage = StyleCachedImage::create(m_cachedToImage.get());
190    RefPtr<StyleCachedImage> fromStyledImage = StyleCachedImage::create(m_cachedFromImage.get());
191
192    auto fromImageValue = CSSImageValue::create(m_cachedFromImage->url(), fromStyledImage.get());
193    auto toImageValue = CSSImageValue::create(m_cachedToImage->url(), toStyledImage.get());
194
195    RefPtr<CSSCrossfadeValue> crossfadeValue = CSSCrossfadeValue::create(WTF::move(fromImageValue), WTF::move(toImageValue));
196
197    double fromPercentage = from.m_percentageValue->getDoubleValue();
198    if (from.m_percentageValue->isPercentage())
199        fromPercentage /= 100.0;
200    double toPercentage = m_percentageValue->getDoubleValue();
201    if (m_percentageValue->isPercentage())
202        toPercentage /= 100.0;
203    crossfadeValue->setPercentage(CSSPrimitiveValue::create(blendFunc(fromPercentage, toPercentage, progress), CSSPrimitiveValue::CSS_NUMBER));
204    return crossfadeValue.release();
205}
206
207bool CSSCrossfadeValue::equals(const CSSCrossfadeValue& other) const
208{
209    return equalInputImages(other)
210        && compareCSSValuePtr(m_percentageValue, other.m_percentageValue);
211}
212
213
214bool CSSCrossfadeValue::equalInputImages(const CSSCrossfadeValue& other) const
215{
216    return compareCSSValuePtr(m_fromValue, other.m_fromValue)
217        && compareCSSValuePtr(m_toValue, other.m_toValue);
218}
219
220} // namespace WebCore
221