Commit a3e8310c authored by vitaliii's avatar vitaliii Committed by Commit bot

Support WebP images in the IOSImageDecoderImpl

WebP images are not supported natively on iOS.
This CL adds WebP images decoding to
IOSImageDecoderImpl using webp_decoder.

Most of the work had been done by
markusheintz@chromium.org and then I took over the CL.

BUG=625081

Review-Url: https://codereview.chromium.org/2324793002
Cr-Commit-Position: refs/heads/master@{#423829}
parent 1f7fce2d
...@@ -67,6 +67,7 @@ test("ios_chrome_unittests") { ...@@ -67,6 +67,7 @@ test("ios_chrome_unittests") {
"browser/snapshots/snapshot_cache_unittest.mm", "browser/snapshots/snapshot_cache_unittest.mm",
"browser/snapshots/snapshots_util_unittest.mm", "browser/snapshots/snapshots_util_unittest.mm",
"browser/ssl/ios_ssl_error_handler_unittest.mm", "browser/ssl/ios_ssl_error_handler_unittest.mm",
"browser/suggestions/ios_image_decoder_impl_unittest.mm",
"browser/translate/translate_service_ios_unittest.cc", "browser/translate/translate_service_ios_unittest.cc",
"browser/ui/alert_coordinator/action_sheet_coordinator_unittest.mm", "browser/ui/alert_coordinator/action_sheet_coordinator_unittest.mm",
"browser/ui/alert_coordinator/alert_coordinator_unittest.mm", "browser/ui/alert_coordinator/alert_coordinator_unittest.mm",
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/json/json_reader.h" #include "base/json/json_reader.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/memory/singleton.h" #include "base/memory/singleton.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "base/values.h" #include "base/values.h"
#include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/browser/bookmark_model.h"
...@@ -50,8 +51,8 @@ using ntp_snippets::NTPSnippetsFetcher; ...@@ -50,8 +51,8 @@ using ntp_snippets::NTPSnippetsFetcher;
using ntp_snippets::NTPSnippetsScheduler; using ntp_snippets::NTPSnippetsScheduler;
using ntp_snippets::NTPSnippetsService; using ntp_snippets::NTPSnippetsService;
using ntp_snippets::NTPSnippetsStatusService; using ntp_snippets::NTPSnippetsStatusService;
using suggestions::CreateIOSImageDecoder;
using suggestions::ImageFetcherImpl; using suggestions::ImageFetcherImpl;
using suggestions::IOSImageDecoderImpl;
namespace { namespace {
...@@ -162,7 +163,7 @@ IOSChromeContentSuggestionsServiceFactory::BuildServiceInstanceFor( ...@@ -162,7 +163,7 @@ IOSChromeContentSuggestionsServiceFactory::BuildServiceInstanceFor(
: google_apis::GetNonStableAPIKey()), : google_apis::GetNonStableAPIKey()),
base::MakeUnique<ImageFetcherImpl>( base::MakeUnique<ImageFetcherImpl>(
request_context.get(), web::WebThread::GetBlockingPool()), request_context.get(), web::WebThread::GetBlockingPool()),
base::MakeUnique<IOSImageDecoderImpl>(), CreateIOSImageDecoder(task_runner),
base::MakeUnique<NTPSnippetsDatabase>(database_dir, task_runner), base::MakeUnique<NTPSnippetsDatabase>(database_dir, task_runner),
base::MakeUnique<NTPSnippetsStatusService>(signin_manager, prefs)); base::MakeUnique<NTPSnippetsStatusService>(signin_manager, prefs));
service->set_ntp_snippets_service(ntp_snippets_service.get()); service->set_ntp_snippets_service(ntp_snippets_service.get());
......
...@@ -5,23 +5,18 @@ ...@@ -5,23 +5,18 @@
#ifndef IOS_CHROME_BROWSER_SUGGESTIONS_IOS_IMAGE_DECODER_IMPL_H_ #ifndef IOS_CHROME_BROWSER_SUGGESTIONS_IOS_IMAGE_DECODER_IMPL_H_
#define IOS_CHROME_BROWSER_SUGGESTIONS_IOS_IMAGE_DECODER_IMPL_H_ #define IOS_CHROME_BROWSER_SUGGESTIONS_IOS_IMAGE_DECODER_IMPL_H_
#include "base/macros.h" #include "base/memory/ref_counted.h"
#include "components/image_fetcher/image_decoder.h" #include "components/image_fetcher/image_decoder.h"
namespace suggestions { namespace base {
class TaskRunner;
class IOSImageDecoderImpl : public image_fetcher::ImageDecoder { }
public:
IOSImageDecoderImpl();
~IOSImageDecoderImpl() override;
void DecodeImage( namespace suggestions {
const std::string& image_data,
const image_fetcher::ImageDecodedCallback& callback) override;
private: // Factory for iOS specific implementation of image_fetcher::ImageDecoder.
DISALLOW_COPY_AND_ASSIGN(IOSImageDecoderImpl); std::unique_ptr<image_fetcher::ImageDecoder> CreateIOSImageDecoder(
}; scoped_refptr<base::TaskRunner> task_runner);
} // namespace suggestions } // namespace suggestions
......
...@@ -7,29 +7,148 @@ ...@@ -7,29 +7,148 @@
#include <UIKit/UIKit.h> #include <UIKit/UIKit.h>
#include "base/callback.h" #include "base/callback.h"
#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "ios/chrome/browser/webp_transcode/webp_decoder.h"
#include "ios/web/public/web_thread.h"
#include "ui/gfx/image/image.h" #include "ui/gfx/image/image.h"
namespace {
class WebpDecoderDelegate : public webp_transcode::WebpDecoder::Delegate {
public:
WebpDecoderDelegate() = default;
NSData* data() const { return decoded_image_; }
// WebpDecoder::Delegate methods
void OnFinishedDecoding(bool success) override {
if (!success)
decoded_image_.reset();
}
void SetImageFeatures(
size_t total_size,
webp_transcode::WebpDecoder::DecodedImageFormat format) override {
decoded_image_.reset([[NSMutableData alloc] initWithCapacity:total_size]);
}
void OnDataDecoded(NSData* data) override {
DCHECK(decoded_image_);
[decoded_image_ appendData:data];
}
private:
~WebpDecoderDelegate() override {}
base::scoped_nsobject<NSMutableData> decoded_image_;
DISALLOW_COPY_AND_ASSIGN(WebpDecoderDelegate);
};
// Returns an NSData object containing the decoded image data of the given
// webp_image. Returns nil in case of failure.
base::scoped_nsobject<NSData> DecodeWebpImage(
const base::scoped_nsobject<NSData>& webp_image) {
scoped_refptr<WebpDecoderDelegate> delegate(new WebpDecoderDelegate);
scoped_refptr<webp_transcode::WebpDecoder> decoder(
new webp_transcode::WebpDecoder(delegate.get()));
decoder->OnDataReceived(webp_image);
DLOG_IF(ERROR, !delegate->data()) << "WebP image decoding failed.";
return base::scoped_nsobject<NSData>([delegate->data() retain]);
}
// Returns true if the given image_data is a WebP image.
//
// Every WebP file contains a 12 byte file header in the beginning of the file.
// A WebP file header starts with the four ASCII characters "RIFF". The next
// four bytes contain the image size and the last four header bytes contain the
// four ASCII characters "WEBP".
//
// WebP file header:
// 1 1
// Byte Nr. 0 1 2 3 4 5 6 7 8 9 0 1
// Byte value [ R I F F ? ? ? ? W E B P ]
//
// For more information see:
// https://developers.google.com/speed/webp/docs/riff_container#webp_file_header
bool IsWebpImage(const std::string& image_data) {
if (image_data.length() < 12)
return false;
return image_data.compare(0, 4, "RIFF") == 0 &&
image_data.compare(8, 4, "WEBP") == 0;
}
} // namespace
namespace suggestions { namespace suggestions {
IOSImageDecoderImpl::IOSImageDecoderImpl() {} class IOSImageDecoderImpl : public image_fetcher::ImageDecoder {
public:
explicit IOSImageDecoderImpl(scoped_refptr<base::TaskRunner> task_runner);
~IOSImageDecoderImpl() override;
void DecodeImage(
const std::string& image_data,
const image_fetcher::ImageDecodedCallback& callback) override;
private:
void CreateUIImageAndRunCallback(
const image_fetcher::ImageDecodedCallback& callback,
const base::scoped_nsobject<NSData>& image_data);
// The task runner used to decode images if necessary.
const scoped_refptr<base::TaskRunner> task_runner_;
// The WeakPtrFactory is used to cancel callbacks if ImageFetcher is
// destroyed during WebP decoding.
base::WeakPtrFactory<IOSImageDecoderImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(IOSImageDecoderImpl);
};
IOSImageDecoderImpl::IOSImageDecoderImpl(
scoped_refptr<base::TaskRunner> task_runner)
: task_runner_(std::move(task_runner)), weak_factory_(this) {
DCHECK(task_runner_.get());
}
IOSImageDecoderImpl::~IOSImageDecoderImpl() {} IOSImageDecoderImpl::~IOSImageDecoderImpl() {}
void IOSImageDecoderImpl::DecodeImage( void IOSImageDecoderImpl::DecodeImage(
const std::string& image_data, const std::string& image_data,
const image_fetcher::ImageDecodedCallback& callback) { const image_fetcher::ImageDecodedCallback& callback) {
// Convert the |image_data| std::string to a NSData buffer. // Convert the |image_data| std::string to an NSData buffer.
NSData* data = base::scoped_nsobject<NSData> data(
[NSData dataWithBytesNoCopy:const_cast<char*>(image_data.c_str()) [[NSData dataWithBytesNoCopy:const_cast<char*>(image_data.c_str())
length:image_data.length() length:image_data.length()
freeWhenDone:NO]; freeWhenDone:NO] retain]);
// Decode the Image using UIImage. // The WebP image format is not supported by iOS natively. Therefore WebP
if (data) { // images need to be decoded explicitly,
// Most likely always returns 1x images. if (IsWebpImage(image_data)) {
UIImage* ui_image = [UIImage imageWithData:data scale:1]; base::PostTaskAndReplyWithResult(
task_runner_.get(), FROM_HERE, base::Bind(&DecodeWebpImage, data),
base::Bind(&IOSImageDecoderImpl::CreateUIImageAndRunCallback,
weak_factory_.GetWeakPtr(), callback));
} else {
CreateUIImageAndRunCallback(callback, data);
}
}
void IOSImageDecoderImpl::CreateUIImageAndRunCallback(
const image_fetcher::ImageDecodedCallback& callback,
const base::scoped_nsobject<NSData>& image_data) {
// Decode the image data using UIImage.
if (image_data) {
// "Most likely" always returns 1x images.
UIImage* ui_image = [UIImage imageWithData:image_data scale:1];
if (ui_image) { if (ui_image) {
gfx::Image gfx_image(ui_image); // This constructor does not retain the image, but expects to take the
// ownership, therefore, |ui_image| is retained here, but not released
// afterwards.
gfx::Image gfx_image([ui_image retain]);
callback.Run(gfx_image); callback.Run(gfx_image);
return; return;
} }
...@@ -38,4 +157,9 @@ void IOSImageDecoderImpl::DecodeImage( ...@@ -38,4 +157,9 @@ void IOSImageDecoderImpl::DecodeImage(
callback.Run(empty_image); callback.Run(empty_image);
} }
std::unique_ptr<image_fetcher::ImageDecoder> CreateIOSImageDecoder(
scoped_refptr<base::TaskRunner> task_runner) {
return base::MakeUnique<IOSImageDecoderImpl>(std::move(task_runner));
}
} // namespace suggestions } // namespace suggestions
// Copyright 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/suggestions/ios_image_decoder_impl.h"
#import <UIKit/UIKit.h>
#include "base/bind.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#include "ui/gfx/image/image.h"
namespace {
static unsigned char kJPGImage[] = {
255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 1, 1, 0,
72, 0, 72, 0, 0, 255, 254, 0, 19, 67, 114, 101, 97, 116, 101,
100, 32, 119, 105, 116, 104, 32, 71, 73, 77, 80, 255, 219, 0, 67,
0, 5, 3, 4, 4, 4, 3, 5, 4, 4, 4, 5, 5, 5, 6,
7, 12, 8, 7, 7, 7, 7, 15, 11, 11, 9, 12, 17, 15, 18,
18, 17, 15, 17, 17, 19, 22, 28, 23, 19, 20, 26, 21, 17, 17,
24, 33, 24, 26, 29, 29, 31, 31, 31, 19, 23, 34, 36, 34, 30,
36, 28, 30, 31, 30, 255, 219, 0, 67, 1, 5, 5, 5, 7, 6,
7, 14, 8, 8, 14, 30, 20, 17, 20, 30, 30, 30, 30, 30, 30,
30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 255,
192, 0, 17, 8, 0, 1, 0, 1, 3, 1, 34, 0, 2, 17, 1,
3, 17, 1, 255, 196, 0, 21, 0, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 255, 196, 0, 20,
16, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 255, 196, 0, 20, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 196, 0, 20, 17,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 255, 218, 0, 12, 3, 1, 0, 2, 17, 3, 17, 0, 63,
0, 178, 192, 7, 255, 217};
static unsigned char kWEBPImage[] = {
82, 73, 70, 70, 74, 0, 0, 0, 87, 69, 66, 80, 86, 80, 56, 88, 10,
0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 76, 80, 72,
12, 0, 0, 0, 1, 7, 16, 17, 253, 15, 68, 68, 255, 3, 0, 0, 86,
80, 56, 32, 24, 0, 0, 0, 48, 1, 0, 157, 1, 42, 1, 0, 1, 0,
3, 0, 52, 37, 164, 0, 3, 112, 0, 254, 251, 253, 80, 0};
} // namespace
namespace suggestions {
class IOSImageDecoderImplTest : public PlatformTest {
public:
void OnImageDecoded(const gfx::Image& image) { decoded_image_ = image; }
protected:
IOSImageDecoderImplTest()
: pool_(new base::SequencedWorkerPool(2,
"TestPool",
base::TaskPriority::USER_VISIBLE)) {
ios_image_decoder_impl_ = CreateIOSImageDecoder(pool_);
}
~IOSImageDecoderImplTest() override { pool_->Shutdown(); }
base::MessageLoop loop_;
scoped_refptr<base::SequencedWorkerPool> pool_;
std::unique_ptr<image_fetcher::ImageDecoder> ios_image_decoder_impl_;
gfx::Image decoded_image_;
};
TEST_F(IOSImageDecoderImplTest, JPGImage) {
ASSERT_TRUE(decoded_image_.IsEmpty());
std::string image_data =
std::string(reinterpret_cast<char*>(kJPGImage), sizeof(kJPGImage));
ios_image_decoder_impl_->DecodeImage(
image_data, base::Bind(&IOSImageDecoderImplTest::OnImageDecoded,
base::Unretained(this)));
pool_->FlushForTesting();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(decoded_image_.IsEmpty());
}
TEST_F(IOSImageDecoderImplTest, WebpImage) {
ASSERT_TRUE(decoded_image_.IsEmpty());
std::string image_data =
std::string(reinterpret_cast<char*>(kWEBPImage), sizeof(kWEBPImage));
ios_image_decoder_impl_->DecodeImage(
image_data, base::Bind(&IOSImageDecoderImplTest::OnImageDecoded,
base::Unretained(this)));
pool_->FlushForTesting();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(decoded_image_.IsEmpty());
}
} // namespace suggestions
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment