| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/base/resource/resource_bundle.h" | 5 #include "ui/base/resource/resource_bundle.h" |
| 6 | 6 |
| 7 #include <vector> | 7 #include <vector> |
| 8 | 8 |
| 9 #include "base/command_line.h" | 9 #include "base/command_line.h" |
| 10 #include "base/file_util.h" | 10 #include "base/file_util.h" |
| 11 #include "base/logging.h" | 11 #include "base/logging.h" |
| 12 #include "base/memory/ref_counted_memory.h" | 12 #include "base/memory/ref_counted_memory.h" |
| 13 #include "base/metrics/histogram.h" | 13 #include "base/metrics/histogram.h" |
| 14 #include "base/path_service.h" | 14 #include "base/path_service.h" |
| 15 #include "base/stl_util.h" | 15 #include "base/stl_util.h" |
| 16 #include "base/string_piece.h" | 16 #include "base/string_piece.h" |
| 17 #include "base/synchronization/lock.h" | 17 #include "base/synchronization/lock.h" |
| 18 #include "base/utf_string_conversions.h" | 18 #include "base/utf_string_conversions.h" |
| 19 #include "build/build_config.h" | 19 #include "build/build_config.h" |
| 20 #include "net/base/big_endian.h" |
| 20 #include "skia/ext/image_operations.h" | 21 #include "skia/ext/image_operations.h" |
| 21 #include "third_party/skia/include/core/SkBitmap.h" | 22 #include "third_party/skia/include/core/SkBitmap.h" |
| 22 #include "ui/base/l10n/l10n_util.h" | 23 #include "ui/base/l10n/l10n_util.h" |
| 23 #include "ui/base/layout.h" | 24 #include "ui/base/layout.h" |
| 24 #include "ui/base/resource/data_pack.h" | 25 #include "ui/base/resource/data_pack.h" |
| 25 #include "ui/base/ui_base_paths.h" | 26 #include "ui/base/ui_base_paths.h" |
| 26 #include "ui/base/ui_base_switches.h" | 27 #include "ui/base/ui_base_switches.h" |
| 27 #include "ui/gfx/codec/jpeg_codec.h" | 28 #include "ui/gfx/codec/jpeg_codec.h" |
| 28 #include "ui/gfx/codec/png_codec.h" | 29 #include "ui/gfx/codec/png_codec.h" |
| 29 #include "ui/gfx/image/image_skia.h" | 30 #include "ui/gfx/image/image_skia.h" |
| 30 #include "ui/gfx/image/image_skia_source.h" | 31 #include "ui/gfx/image/image_skia_source.h" |
| 32 #include "ui/gfx/safe_integer_conversions.h" |
| 31 #include "ui/gfx/screen.h" | 33 #include "ui/gfx/screen.h" |
| 32 #include "ui/gfx/size_conversions.h" | 34 #include "ui/gfx/size_conversions.h" |
| 33 #include "ui/gfx/skbitmap_operations.h" | 35 #include "ui/gfx/skbitmap_operations.h" |
| 34 | 36 |
| 35 namespace ui { | 37 namespace ui { |
| 36 | 38 |
| 37 namespace { | 39 namespace { |
| 38 | 40 |
| 39 // Font sizes relative to base font. | 41 // Font sizes relative to base font. |
| 40 const int kSmallFontSizeDelta = -2; | 42 const int kSmallFontSizeDelta = -2; |
| 41 const int kMediumFontSizeDelta = 3; | 43 const int kMediumFontSizeDelta = 3; |
| 42 const int kLargeFontSizeDelta = 8; | 44 const int kLargeFontSizeDelta = 8; |
| 43 | 45 |
| 46 // PNG-related constants. |
| 47 const unsigned char kPngMagic[8] = { 0x89, 'P', 'N', 'G', 13, 10, 26, 10 }; |
| 48 const size_t kPngChunkMetadataSize = 12; // length, type, crc32 |
| 49 const unsigned char kPngScaleChunkType[4] = { 'c', 's', 'C', 'l' }; |
| 50 |
| 44 ResourceBundle* g_shared_instance_ = NULL; | 51 ResourceBundle* g_shared_instance_ = NULL; |
| 45 | 52 |
| 46 bool ShouldHighlightMissingScaledResources() { | 53 bool ShouldHighlightMissingScaledResources() { |
| 47 return CommandLine::ForCurrentProcess()->HasSwitch( | 54 return CommandLine::ForCurrentProcess()->HasSwitch( |
| 48 switches::kHighlightMissingScaledResources); | 55 switches::kHighlightMissingScaledResources); |
| 49 } | 56 } |
| 50 | 57 |
| 58 // A wrapper for PNGCodec::Decode that returns information about custom chunks. |
| 59 // For security reasons we can't alter PNGCodec to return this information. Our |
| 60 // PNG files are preprocessed by GRIT to move our custom chunks to the beginning |
| 61 // (before even the PNG header), and we omit them entirely from the data that we |
| 62 // pass on to PNGCodec::Decode. |
| 63 bool DecodePNG(const unsigned char* buf, |
| 64 size_t size, |
| 65 SkBitmap* bitmap, |
| 66 bool* fell_back_to_1x) { |
| 67 *fell_back_to_1x = false; |
| 68 size_t pos = 0; |
| 69 // Scan for special chunks until we find the PNG header. |
| 70 for (;;) { |
| 71 if (size - pos < kPngChunkMetadataSize) |
| 72 return false; |
| 73 if (memcmp(buf + pos, kPngMagic, sizeof(kPngMagic)) == 0) |
| 74 break; |
| 75 uint32 length = 0; |
| 76 net::ReadBigEndian(reinterpret_cast<const char*>(buf + pos), &length); |
| 77 if (size - pos - kPngChunkMetadataSize < length) |
| 78 return false; |
| 79 if (length == 0 && memcmp(buf + pos + sizeof(uint32), kPngScaleChunkType, |
| 80 sizeof(kPngScaleChunkType)) == 0) |
| 81 *fell_back_to_1x = true; |
| 82 pos += length + kPngChunkMetadataSize; |
| 83 } |
| 84 // Pass the rest of the data to the PNG decoder. |
| 85 return gfx::PNGCodec::Decode(buf + pos, size - pos, bitmap); |
| 86 } |
| 87 |
| 51 } // namespace | 88 } // namespace |
| 52 | 89 |
| 53 // An ImageSkiaSource that loads bitmaps for requested scale factor from | 90 // An ImageSkiaSource that loads bitmaps for the requested scale factor from |
| 54 // ResourceBundle on demand for given resource_id. It falls back | 91 // ResourceBundle on demand for a given |resource_id|. If the bitmap for the |
| 55 // to the 1x bitmap if the bitmap for the requested scale factor does not | 92 // requested scale factor does not exist, it will return the 1x bitmap scaled |
| 56 // exist. If the resource for the requested scale factor is not exactly | 93 // by the scale factor. This may lead to broken UI if the correct size of the |
| 57 // |scale_factor| * the size of the 1x resource, it will end up with | 94 // scaled image is not exactly |scale_factor| * the size of the 1x resource. |
| 58 // broken UI because it will be drawn as if the bitmap was the correct size. | 95 // When --highlight-missing-scaled-resources flag is specified, scaled 1x images |
| 59 // When --highlight-missing-scaled-resources flag is specified, it | 96 // are higlighted by blending them with red. |
| 60 // will show the scaled image blended with red instead. | |
| 61 class ResourceBundle::ResourceBundleImageSource : public gfx::ImageSkiaSource { | 97 class ResourceBundle::ResourceBundleImageSource : public gfx::ImageSkiaSource { |
| 62 public: | 98 public: |
| 63 ResourceBundleImageSource(ResourceBundle* rb, | 99 ResourceBundleImageSource(ResourceBundle* rb, int resource_id) |
| 64 int resource_id, | 100 : rb_(rb), resource_id_(resource_id) {} |
| 65 const gfx::Size& size_in_dip) | |
| 66 : rb_(rb), | |
| 67 resource_id_(resource_id), | |
| 68 size_in_dip_(size_in_dip) { | |
| 69 } | |
| 70 virtual ~ResourceBundleImageSource() {} | 101 virtual ~ResourceBundleImageSource() {} |
| 71 | 102 |
| 72 // gfx::ImageSkiaSource overrides: | 103 // gfx::ImageSkiaSource overrides: |
| 73 virtual gfx::ImageSkiaRep GetImageForScale( | 104 virtual gfx::ImageSkiaRep GetImageForScale( |
| 74 ui::ScaleFactor scale_factor) OVERRIDE { | 105 ui::ScaleFactor scale_factor) OVERRIDE { |
| 75 scoped_ptr<SkBitmap> result(rb_->LoadBitmap(resource_id_, scale_factor)); | 106 SkBitmap image; |
| 76 float scale = ui::GetScaleFactorScale(scale_factor); | 107 bool fell_back_to_1x = false; |
| 77 gfx::Size size_in_pixel = gfx::ToFlooredSize(size_in_dip_.Scale(scale)); | 108 bool found = rb_->LoadBitmap(resource_id_, scale_factor, |
| 109 &image, &fell_back_to_1x); |
| 110 if (!found) |
| 111 return gfx::ImageSkiaRep(); |
| 78 | 112 |
| 79 if (scale_factor != SCALE_FACTOR_100P && | 113 if (fell_back_to_1x) { |
| 80 (!result.get() || | 114 // GRIT fell back to the 100% image, so rescale it to the correct size. |
| 81 result->width() != size_in_pixel.width() || | 115 float scale = GetScaleFactorScale(scale_factor); |
| 82 result->height() != size_in_pixel.height())) { | 116 image = skia::ImageOperations::Resize( |
| 83 | 117 image, |
| 84 // If non 1x resource is missing from |image| or is the incorrect | 118 skia::ImageOperations::RESIZE_LANCZOS3, |
| 85 // size and --highlight-missing-scaled-resources is specified, logs | 119 gfx::ToFlooredInt(image.width() * scale), |
| 86 // the resource id and creates a version of the resource at the correct | 120 gfx::ToFlooredInt(image.height() * scale)); |
| 87 // size. Blends the created resource with red to make it | 121 // If --highlight-missing-scaled-resources is specified, log the resource |
| 88 // distinguishable from bitmaps in the resource pak. | 122 // id and blend the created resource with red. |
| 89 if (ShouldHighlightMissingScaledResources()) { | 123 if (ShouldHighlightMissingScaledResources()) { |
| 90 if (!result.get()) { | 124 LOG(ERROR) << "Missing " << scale << "x scaled resource. id=" |
| 91 LOG(ERROR) << "Missing " << scale << "x resource. id=" | 125 << resource_id_; |
| 92 << resource_id_; | |
| 93 } else { | |
| 94 LOG(ERROR) << "Incorrectly sized " << scale << "x resource. id=" | |
| 95 << resource_id_; | |
| 96 } | |
| 97 | |
| 98 scoped_ptr<SkBitmap> bitmap1x( | |
| 99 rb_->LoadBitmap(resource_id_, SCALE_FACTOR_100P)); | |
| 100 DCHECK(bitmap1x.get()); | |
| 101 SkBitmap bitmap_scaled = skia::ImageOperations::Resize( | |
| 102 *bitmap1x, | |
| 103 skia::ImageOperations::RESIZE_LANCZOS3, | |
| 104 size_in_pixel.width(), | |
| 105 size_in_pixel.height()); | |
| 106 | 126 |
| 107 SkBitmap mask; | 127 SkBitmap mask; |
| 108 mask.setConfig(SkBitmap::kARGB_8888_Config, | 128 mask.setConfig(SkBitmap::kARGB_8888_Config, |
| 109 bitmap_scaled.width(), | 129 image.width(), image.height()); |
| 110 bitmap_scaled.height()); | |
| 111 mask.allocPixels(); | 130 mask.allocPixels(); |
| 112 mask.eraseColor(SK_ColorRED); | 131 mask.eraseColor(SK_ColorRED); |
| 113 result.reset(new SkBitmap()); | 132 image = SkBitmapOperations::CreateBlendedBitmap(image, mask, 0.2); |
| 114 *result.get() = SkBitmapOperations::CreateBlendedBitmap( | |
| 115 bitmap_scaled, mask, 0.2); | |
| 116 } else if (!result.get() || result->width() == size_in_dip_.width()) { | |
| 117 // The scaled resource pack may have the 1x image if its grd file | |
| 118 // points to 1x image. Fallback to 1x by returning empty image | |
| 119 // in this case. This 1x image will be scaled when drawn. | |
| 120 return gfx::ImageSkiaRep(); | |
| 121 } | 133 } |
| 122 // If the size of scaled image isn't exactly |scale| * 1x version, | |
| 123 // create ImageSkia as usual. This will end up with | |
| 124 // corrupted visual representation as the size of image doesn't | |
| 125 // match the expected size. | |
| 126 } | 134 } |
| 127 DCHECK(result.get()); | 135 |
| 128 return gfx::ImageSkiaRep(*result.get(), scale_factor); | 136 return gfx::ImageSkiaRep(image, scale_factor); |
| 129 } | 137 } |
| 130 | 138 |
| 131 private: | 139 private: |
| 132 ResourceBundle* rb_; | 140 ResourceBundle* rb_; |
| 133 const int resource_id_; | 141 const int resource_id_; |
| 134 const gfx::Size size_in_dip_; | |
| 135 | 142 |
| 136 DISALLOW_COPY_AND_ASSIGN(ResourceBundleImageSource); | 143 DISALLOW_COPY_AND_ASSIGN(ResourceBundleImageSource); |
| 137 }; | 144 }; |
| 138 | 145 |
| 139 // static | 146 // static |
| 140 std::string ResourceBundle::InitSharedInstanceWithLocale( | 147 std::string ResourceBundle::InitSharedInstanceWithLocale( |
| 141 const std::string& pref_locale, Delegate* delegate) { | 148 const std::string& pref_locale, Delegate* delegate) { |
| 142 DCHECK(g_shared_instance_ == NULL) << "ResourceBundle initialized twice"; | 149 DCHECK(g_shared_instance_ == NULL) << "ResourceBundle initialized twice"; |
| 143 g_shared_instance_ = new ResourceBundle(delegate); | 150 g_shared_instance_ = new ResourceBundle(delegate); |
| 144 | 151 |
| (...skipping 198 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 343 } | 350 } |
| 344 | 351 |
| 345 gfx::Image image; | 352 gfx::Image image; |
| 346 if (delegate_) | 353 if (delegate_) |
| 347 image = delegate_->GetImageNamed(resource_id); | 354 image = delegate_->GetImageNamed(resource_id); |
| 348 | 355 |
| 349 if (image.IsEmpty()) { | 356 if (image.IsEmpty()) { |
| 350 DCHECK(!delegate_ && !data_packs_.empty()) << | 357 DCHECK(!delegate_ && !data_packs_.empty()) << |
| 351 "Missing call to SetResourcesDataDLL?"; | 358 "Missing call to SetResourcesDataDLL?"; |
| 352 | 359 |
| 353 // TODO(oshima): Pick the scale factor from currently used scale factors. | 360 // TODO(oshima): This should be GetPrimaryDisplay().device_scale_factor(), |
| 354 scoped_ptr<SkBitmap> bitmap(LoadBitmap(resource_id, SCALE_FACTOR_100P)); | 361 // but GetPrimaryDisplay() crashes at startup. |
| 355 if (!bitmap.get()) { | 362 ScaleFactor primary_scale_factor = SCALE_FACTOR_100P; |
| 363 // ResourceBundle::GetSharedInstance() is destroyed after the |
| 364 // BrowserMainLoop has finished running. |image_skia| is guaranteed to be |
| 365 // destroyed before the resource bundle is destroyed. |
| 366 gfx::ImageSkia image_skia(new ResourceBundleImageSource(this, resource_id), |
| 367 primary_scale_factor); |
| 368 if (image_skia.isNull()) { |
| 356 LOG(WARNING) << "Unable to load image with id " << resource_id; | 369 LOG(WARNING) << "Unable to load image with id " << resource_id; |
| 357 NOTREACHED(); // Want to assert in debug mode. | 370 NOTREACHED(); // Want to assert in debug mode. |
| 358 // The load failed to retrieve the image; show a debugging red square. | 371 // The load failed to retrieve the image; show a debugging red square. |
| 359 return GetEmptyImage(); | 372 return GetEmptyImage(); |
| 360 } | 373 } |
| 361 | |
| 362 // ResourceBundle::GetSharedInstance() is destroyed after the | |
| 363 // BrowserMainLoop has finished running. |image_skia| is guaranteed to be | |
| 364 // destroyed before the resource bundle is destroyed. | |
| 365 gfx::Size size_in_dip(bitmap->width(), bitmap->height()); | |
| 366 gfx::ImageSkia image_skia( | |
| 367 new ResourceBundleImageSource(this, resource_id, size_in_dip), | |
| 368 size_in_dip); | |
| 369 image_skia.AddRepresentation(gfx::ImageSkiaRep(*bitmap.get(), | |
| 370 SCALE_FACTOR_100P)); | |
| 371 image_skia.SetReadOnly(); | 374 image_skia.SetReadOnly(); |
| 372 image = gfx::Image(image_skia); | 375 image = gfx::Image(image_skia); |
| 373 } | 376 } |
| 374 | 377 |
| 375 // The load was successful, so cache the image. | 378 // The load was successful, so cache the image. |
| 376 base::AutoLock lock_scope(*images_and_fonts_lock_); | 379 base::AutoLock lock_scope(*images_and_fonts_lock_); |
| 377 | 380 |
| 378 // Another thread raced the load and has already cached the image. | 381 // Another thread raced the load and has already cached the image. |
| 379 if (images_.count(resource_id)) | 382 if (images_.count(resource_id)) |
| 380 return images_[resource_id]; | 383 return images_[resource_id]; |
| (...skipping 207 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 588 | 591 |
| 589 if (!large_bold_font_.get()) { | 592 if (!large_bold_font_.get()) { |
| 590 large_bold_font_.reset(new gfx::Font()); | 593 large_bold_font_.reset(new gfx::Font()); |
| 591 *large_bold_font_ = | 594 *large_bold_font_ = |
| 592 base_font_->DeriveFont(kLargeFontSizeDelta, | 595 base_font_->DeriveFont(kLargeFontSizeDelta, |
| 593 base_font_->GetStyle() | gfx::Font::BOLD); | 596 base_font_->GetStyle() | gfx::Font::BOLD); |
| 594 } | 597 } |
| 595 } | 598 } |
| 596 } | 599 } |
| 597 | 600 |
| 598 SkBitmap* ResourceBundle::LoadBitmap(const ResourceHandle& data_handle, | 601 bool ResourceBundle::LoadBitmap(const ResourceHandle& data_handle, |
| 599 int resource_id) const { | 602 int resource_id, |
| 603 SkBitmap* bitmap, |
| 604 bool* fell_back_to_1x) const { |
| 605 DCHECK(fell_back_to_1x); |
| 600 scoped_refptr<base::RefCountedMemory> memory( | 606 scoped_refptr<base::RefCountedMemory> memory( |
| 601 data_handle.GetStaticMemory(resource_id)); | 607 data_handle.GetStaticMemory(resource_id)); |
| 602 if (!memory) | 608 if (!memory) |
| 603 return NULL; | 609 return false; |
| 604 | 610 |
| 605 SkBitmap bitmap; | 611 if (DecodePNG(memory->front(), memory->size(), bitmap, fell_back_to_1x)) |
| 606 if (gfx::PNGCodec::Decode(memory->front(), memory->size(), &bitmap)) | 612 return true; |
| 607 return new SkBitmap(bitmap); | |
| 608 | 613 |
| 609 #if !defined(OS_IOS) | 614 #if !defined(OS_IOS) |
| 610 // iOS does not compile or use the JPEG codec. On other platforms, | 615 // iOS does not compile or use the JPEG codec. On other platforms, |
| 611 // 99% of our assets are PNGs, however fallback to JPEG. | 616 // 99% of our assets are PNGs, however fallback to JPEG. |
| 612 SkBitmap* allocated_bitmap = | 617 scoped_ptr<SkBitmap> jpeg_bitmap( |
| 613 gfx::JPEGCodec::Decode(memory->front(), memory->size()); | 618 gfx::JPEGCodec::Decode(memory->front(), memory->size())); |
| 614 if (allocated_bitmap) | 619 if (jpeg_bitmap.get()) { |
| 615 return allocated_bitmap; | 620 bitmap->swap(*jpeg_bitmap.get()); |
| 621 *fell_back_to_1x = false; |
| 622 return true; |
| 623 } |
| 616 #endif | 624 #endif |
| 617 | 625 |
| 618 NOTREACHED() << "Unable to decode theme image resource " << resource_id; | 626 NOTREACHED() << "Unable to decode theme image resource " << resource_id; |
| 619 return NULL; | 627 return false; |
| 620 } | 628 } |
| 621 | 629 |
| 622 SkBitmap* ResourceBundle::LoadBitmap(int resource_id, | 630 bool ResourceBundle::LoadBitmap(int resource_id, |
| 623 ScaleFactor scale_factor) const { | 631 ScaleFactor scale_factor, |
| 632 SkBitmap* bitmap, |
| 633 bool* fell_back_to_1x) const { |
| 634 DCHECK(fell_back_to_1x); |
| 624 for (size_t i = 0; i < data_packs_.size(); ++i) { | 635 for (size_t i = 0; i < data_packs_.size(); ++i) { |
| 625 if (data_packs_[i]->GetScaleFactor() == scale_factor) { | 636 if (data_packs_[i]->GetScaleFactor() == scale_factor) { |
| 626 SkBitmap* bitmap = LoadBitmap(*data_packs_[i], resource_id); | 637 if (LoadBitmap(*data_packs_[i], resource_id, bitmap, fell_back_to_1x)) |
| 627 if (bitmap) | 638 return true; |
| 628 return bitmap; | |
| 629 } | 639 } |
| 630 } | 640 } |
| 631 return NULL; | 641 return false; |
| 632 } | 642 } |
| 633 | 643 |
| 634 gfx::Image& ResourceBundle::GetEmptyImage() { | 644 gfx::Image& ResourceBundle::GetEmptyImage() { |
| 635 base::AutoLock lock(*images_and_fonts_lock_); | 645 base::AutoLock lock(*images_and_fonts_lock_); |
| 636 | 646 |
| 637 if (empty_image_.IsEmpty()) { | 647 if (empty_image_.IsEmpty()) { |
| 638 // The placeholder bitmap is bright red so people notice the problem. | 648 // The placeholder bitmap is bright red so people notice the problem. |
| 639 SkBitmap bitmap; | 649 SkBitmap bitmap; |
| 640 bitmap.setConfig(SkBitmap::kARGB_8888_Config, 32, 32); | 650 bitmap.setConfig(SkBitmap::kARGB_8888_Config, 32, 32); |
| 641 bitmap.allocPixels(); | 651 bitmap.allocPixels(); |
| 642 bitmap.eraseARGB(255, 255, 0, 0); | 652 bitmap.eraseARGB(255, 255, 0, 0); |
| 643 empty_image_ = gfx::Image(bitmap); | 653 empty_image_ = gfx::Image(bitmap); |
| 644 } | 654 } |
| 645 return empty_image_; | 655 return empty_image_; |
| 646 } | 656 } |
| 647 | 657 |
| 648 } // namespace ui | 658 } // namespace ui |
| OLD | NEW |