| /* |
| * Copyright (C) 2011 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/css/css_crossfade_value.h" |
| |
| #include "third_party/blink/renderer/core/css/css_image_value.h" |
| #include "third_party/blink/renderer/core/layout/layout_object.h" |
| #include "third_party/blink/renderer/core/style/style_fetched_image.h" |
| #include "third_party/blink/renderer/core/svg/graphics/svg_image_for_container.h" |
| #include "third_party/blink/renderer/platform/graphics/crossfade_generated_image.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| |
| namespace blink { |
| namespace cssvalue { |
| |
| static bool SubimageIsPending(const CSSValue& value) { |
| if (auto* image_value = DynamicTo<CSSImageValue>(value)) |
| return image_value->IsCachePending(); |
| |
| if (auto* image_generator_value = DynamicTo<CSSImageGeneratorValue>(value)) |
| return image_generator_value->IsPending(); |
| |
| NOTREACHED(); |
| |
| return false; |
| } |
| |
| static bool SubimageKnownToBeOpaque(const CSSValue& value, |
| const Document& document, |
| const ComputedStyle& style) { |
| if (auto* image_value = DynamicTo<CSSImageValue>(value)) |
| return image_value->KnownToBeOpaque(document, style); |
| |
| if (auto* img_generator_value = DynamicTo<CSSImageGeneratorValue>(value)) |
| return img_generator_value->KnownToBeOpaque(document, style); |
| |
| NOTREACHED(); |
| |
| return false; |
| } |
| |
| static ImageResourceContent* CachedImageForCSSValue(CSSValue* value, |
| const Document& document) { |
| if (!value) |
| return nullptr; |
| |
| if (auto* image_value = DynamicTo<CSSImageValue>(value)) { |
| StyleImage* style_image_resource = |
| image_value->CacheImage(document, FetchParameters::kAllowPlaceholder); |
| if (!style_image_resource) |
| return nullptr; |
| |
| return style_image_resource->CachedImage(); |
| } |
| |
| if (auto* img_generator_value = DynamicTo<CSSImageGeneratorValue>(value)) { |
| img_generator_value->LoadSubimages(document); |
| // FIXME: Handle CSSImageGeneratorValue (and thus cross-fades with gradients |
| // and canvas). |
| return nullptr; |
| } |
| |
| NOTREACHED(); |
| |
| return nullptr; |
| } |
| |
| static Image* RenderableImageForCSSValue(CSSValue* value, |
| const Document& document) { |
| ImageResourceContent* cached_image = CachedImageForCSSValue(value, document); |
| |
| if (!cached_image || cached_image->ErrorOccurred() || |
| cached_image->GetImage()->IsNull()) |
| return nullptr; |
| |
| return cached_image->GetImage(); |
| } |
| |
| static KURL UrlForCSSValue(const CSSValue& value) { |
| auto* image_value = DynamicTo<CSSImageValue>(value); |
| if (!image_value) |
| return KURL(); |
| |
| return KURL(image_value->Url()); |
| } |
| |
| CSSCrossfadeValue::CSSCrossfadeValue(CSSValue* from_value, |
| CSSValue* to_value, |
| CSSPrimitiveValue* percentage_value) |
| : CSSImageGeneratorValue(kCrossfadeClass), |
| from_value_(from_value), |
| to_value_(to_value), |
| percentage_value_(percentage_value), |
| cached_from_image_(nullptr), |
| cached_to_image_(nullptr), |
| crossfade_subimage_observer_(this) {} |
| |
| CSSCrossfadeValue::~CSSCrossfadeValue() = default; |
| |
| void CSSCrossfadeValue::Dispose() { |
| if (cached_from_image_) { |
| cached_from_image_->RemoveObserver(&crossfade_subimage_observer_); |
| cached_from_image_ = nullptr; |
| } |
| if (cached_to_image_) { |
| cached_to_image_->RemoveObserver(&crossfade_subimage_observer_); |
| cached_to_image_ = nullptr; |
| } |
| } |
| |
| String CSSCrossfadeValue::CustomCSSText() const { |
| StringBuilder result; |
| result.Append("-webkit-cross-fade("); |
| result.Append(from_value_->CssText()); |
| result.Append(", "); |
| result.Append(to_value_->CssText()); |
| result.Append(", "); |
| result.Append(percentage_value_->CssText()); |
| result.Append(')'); |
| return result.ToString(); |
| } |
| |
| CSSCrossfadeValue* CSSCrossfadeValue::ComputedCSSValue( |
| const ComputedStyle& style, |
| bool allow_visited_style) { |
| CSSValue* from_value = from_value_; |
| if (auto* from_image_value = DynamicTo<CSSImageValue>(from_value_.Get())) { |
| from_value = from_image_value->ValueWithURLMadeAbsolute(); |
| } else if (auto* from_generator_value = |
| DynamicTo<CSSImageGeneratorValue>(from_value_.Get())) { |
| from_value = |
| from_generator_value->ComputedCSSValue(style, allow_visited_style); |
| } |
| CSSValue* to_value = to_value_; |
| if (auto* to_image_value = DynamicTo<CSSImageValue>(to_value_.Get())) { |
| to_value = to_image_value->ValueWithURLMadeAbsolute(); |
| } else if (auto* to_generator_value = |
| DynamicTo<CSSImageGeneratorValue>(to_value_.Get())) { |
| to_value = to_generator_value->ComputedCSSValue(style, allow_visited_style); |
| } |
| return MakeGarbageCollected<CSSCrossfadeValue>(from_value, to_value, |
| percentage_value_); |
| } |
| |
| FloatSize CSSCrossfadeValue::FixedSize( |
| const Document& document, |
| const FloatSize& default_object_size) const { |
| Image* from_image = RenderableImageForCSSValue(from_value_.Get(), document); |
| Image* to_image = RenderableImageForCSSValue(to_value_.Get(), document); |
| |
| if (!from_image || !to_image) |
| return FloatSize(); |
| |
| FloatSize from_image_size(from_image->Size()); |
| FloatSize to_image_size(to_image->Size()); |
| |
| if (auto* from_svg_image = DynamicTo<SVGImage>(from_image)) { |
| from_image_size = from_svg_image->ConcreteObjectSize(default_object_size); |
| } |
| |
| if (auto* to_svg_image = DynamicTo<SVGImage>(to_image)) { |
| to_image_size = to_svg_image->ConcreteObjectSize(default_object_size); |
| } |
| |
| // Rounding issues can cause transitions between images of equal size to |
| // return a different fixed size; avoid performing the interpolation if the |
| // images are the same size. |
| if (from_image_size == to_image_size) |
| return from_image_size; |
| |
| float percentage = percentage_value_->GetFloatValue(); |
| float inverse_percentage = 1 - percentage; |
| |
| return FloatSize(from_image_size.Width() * inverse_percentage + |
| to_image_size.Width() * percentage, |
| from_image_size.Height() * inverse_percentage + |
| to_image_size.Height() * percentage); |
| } |
| |
| bool CSSCrossfadeValue::IsPending() const { |
| return SubimageIsPending(*from_value_) || SubimageIsPending(*to_value_); |
| } |
| |
| bool CSSCrossfadeValue::KnownToBeOpaque(const Document& document, |
| const ComputedStyle& style) const { |
| return SubimageKnownToBeOpaque(*from_value_, document, style) && |
| SubimageKnownToBeOpaque(*to_value_, document, style); |
| } |
| |
| void CSSCrossfadeValue::LoadSubimages(const Document& document) { |
| ImageResourceContent* old_cached_from_image = cached_from_image_; |
| ImageResourceContent* old_cached_to_image = cached_to_image_; |
| |
| cached_from_image_ = CachedImageForCSSValue(from_value_.Get(), document); |
| cached_to_image_ = CachedImageForCSSValue(to_value_.Get(), document); |
| |
| if (cached_from_image_ != old_cached_from_image) { |
| if (old_cached_from_image) |
| old_cached_from_image->RemoveObserver(&crossfade_subimage_observer_); |
| if (cached_from_image_) |
| cached_from_image_->AddObserver(&crossfade_subimage_observer_); |
| } |
| |
| if (cached_to_image_ != old_cached_to_image) { |
| if (old_cached_to_image) |
| old_cached_to_image->RemoveObserver(&crossfade_subimage_observer_); |
| if (cached_to_image_) |
| cached_to_image_->AddObserver(&crossfade_subimage_observer_); |
| } |
| |
| crossfade_subimage_observer_.SetReady(true); |
| } |
| |
| scoped_refptr<Image> CSSCrossfadeValue::GetImage( |
| const ImageResourceObserver& client, |
| const Document& document, |
| const ComputedStyle&, |
| const FloatSize& size) const { |
| if (size.IsEmpty()) |
| return nullptr; |
| |
| Image* from_image = RenderableImageForCSSValue(from_value_.Get(), document); |
| Image* to_image = RenderableImageForCSSValue(to_value_.Get(), document); |
| |
| if (!from_image || !to_image) |
| return Image::NullImage(); |
| |
| scoped_refptr<Image> from_image_ref(from_image); |
| scoped_refptr<Image> to_image_ref(to_image); |
| |
| if (auto* from_svg_image = DynamicTo<SVGImage>(from_image)) { |
| from_image_ref = SVGImageForContainer::Create(from_svg_image, size, 1, |
| UrlForCSSValue(*from_value_)); |
| } |
| |
| if (auto* to_svg_image = DynamicTo<SVGImage>(to_image)) { |
| to_image_ref = SVGImageForContainer::Create(to_svg_image, size, 1, |
| UrlForCSSValue(*to_value_)); |
| } |
| |
| return CrossfadeGeneratedImage::Create(from_image_ref, to_image_ref, |
| percentage_value_->GetFloatValue(), |
| FixedSize(document, size), size); |
| } |
| |
| void CSSCrossfadeValue::CrossfadeChanged( |
| ImageResourceObserver::CanDeferInvalidation defer) { |
| for (const auto& curr : Clients()) { |
| ImageResourceObserver* client = |
| const_cast<ImageResourceObserver*>(curr.key); |
| client->ImageChanged(static_cast<WrappedImagePtr>(this), defer); |
| } |
| } |
| |
| bool CSSCrossfadeValue::WillRenderImage() const { |
| for (const auto& curr : Clients()) { |
| if (const_cast<ImageResourceObserver*>(curr.key)->WillRenderImage()) |
| return true; |
| } |
| return false; |
| } |
| |
| void CSSCrossfadeValue::CrossfadeSubimageObserverProxy::ImageChanged( |
| ImageResourceContent*, |
| CanDeferInvalidation defer) { |
| if (ready_) |
| owner_value_->CrossfadeChanged(defer); |
| } |
| |
| bool CSSCrossfadeValue::CrossfadeSubimageObserverProxy::WillRenderImage() { |
| // If the images are not ready/loaded we won't paint them. If the images |
| // are ready then ask the clients. |
| return ready_ && owner_value_->WillRenderImage(); |
| } |
| |
| bool CSSCrossfadeValue::HasFailedOrCanceledSubresources() const { |
| if (cached_from_image_ && cached_from_image_->LoadFailedOrCanceled()) |
| return true; |
| if (cached_to_image_ && cached_to_image_->LoadFailedOrCanceled()) |
| return true; |
| return false; |
| } |
| |
| bool CSSCrossfadeValue::Equals(const CSSCrossfadeValue& other) const { |
| return DataEquivalent(from_value_, other.from_value_) && |
| DataEquivalent(to_value_, other.to_value_) && |
| DataEquivalent(percentage_value_, other.percentage_value_); |
| } |
| |
| void CSSCrossfadeValue::TraceAfterDispatch(blink::Visitor* visitor) { |
| visitor->Trace(from_value_); |
| visitor->Trace(to_value_); |
| visitor->Trace(percentage_value_); |
| visitor->Trace(cached_from_image_); |
| visitor->Trace(cached_to_image_); |
| visitor->Trace(crossfade_subimage_observer_); |
| CSSImageGeneratorValue::TraceAfterDispatch(visitor); |
| } |
| |
| } // namespace cssvalue |
| } // namespace blink |