[go: nahoru, domu]

[3/5] Add translated rasterization support for RasterBuffer & below

This CL implements the muscle of transformed rasterization. RasterBuffer now
accepts a translation in addition to a scale factor when rasterizing into a
backing.

Arbitrary raster transformation is not planned for near future. Thus only
implementing scale & translate which already helps many common cases.

CQ_INCLUDE_TRYBOTS=master.tryserver.blink:linux_trusty_blink_rel

Review-Url: https://codereview.chromium.org/2563743004
Cr-Commit-Position: refs/heads/master@{#461673}
diff --git a/cc/benchmarks/rasterize_and_record_benchmark_impl.cc b/cc/benchmarks/rasterize_and_record_benchmark_impl.cc
index 0144636..c41d38e 100644
--- a/cc/benchmarks/rasterize_and_record_benchmark_impl.cc
+++ b/cc/benchmarks/rasterize_and_record_benchmark_impl.cc
@@ -17,6 +17,7 @@
 #include "cc/trees/layer_tree_host_common.h"
 #include "cc/trees/layer_tree_host_impl.h"
 #include "cc/trees/layer_tree_impl.h"
+#include "ui/gfx/geometry/axis_transform2d.h"
 #include "ui/gfx/geometry/rect.h"
 
 namespace cc {
@@ -52,9 +53,10 @@
       bitmap.allocPixels(SkImageInfo::MakeN32Premul(content_rect.width(),
                                                     content_rect.height()));
       SkCanvas canvas(bitmap);
-      raster_source->PlaybackToCanvas(&canvas, gfx::ColorSpace(), content_rect,
-                                      content_rect, contents_scale,
-                                      RasterSource::PlaybackSettings());
+      raster_source->PlaybackToCanvas(
+          &canvas, gfx::ColorSpace(), content_rect, content_rect,
+          gfx::AxisTransform2d(contents_scale, gfx::Vector2dF()),
+          RasterSource::PlaybackSettings());
 
       timer.NextLap();
     } while (!timer.HasTimeLimitExpired());
diff --git a/cc/output/software_renderer.cc b/cc/output/software_renderer.cc
index 8bd1e381..95fa085 100644
--- a/cc/output/software_renderer.cc
+++ b/cc/output/software_renderer.cc
@@ -29,6 +29,7 @@
 #include "third_party/skia/include/core/SkPoint.h"
 #include "third_party/skia/include/core/SkShader.h"
 #include "third_party/skia/include/effects/SkLayerRasterizer.h"
+#include "ui/gfx/geometry/axis_transform2d.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/skia_util.h"
 #include "ui/gfx/transform.h"
@@ -362,11 +363,15 @@
                                               disable_image_filtering);
     quad->raster_source->PlaybackToCanvas(
         &filtered_canvas, canvas_color_space, quad->content_rect,
-        quad->content_rect, quad->contents_scale, playback_settings);
+        quad->content_rect,
+        gfx::AxisTransform2d(quad->contents_scale, gfx::Vector2dF()),
+        playback_settings);
   } else {
     quad->raster_source->PlaybackToCanvas(
         current_canvas_, canvas_color_space, quad->content_rect,
-        quad->content_rect, quad->contents_scale, playback_settings);
+        quad->content_rect,
+        gfx::AxisTransform2d(quad->contents_scale, gfx::Vector2dF()),
+        playback_settings);
   }
 }
 
diff --git a/cc/raster/bitmap_raster_buffer_provider.cc b/cc/raster/bitmap_raster_buffer_provider.cc
index a69ad18..470e6e03 100644
--- a/cc/raster/bitmap_raster_buffer_provider.cc
+++ b/cc/raster/bitmap_raster_buffer_provider.cc
@@ -40,7 +40,7 @@
       const gfx::Rect& raster_full_rect,
       const gfx::Rect& raster_dirty_rect,
       uint64_t new_content_id,
-      float scale,
+      const gfx::AxisTransform2d& transform,
       const RasterSource::PlaybackSettings& playback_settings) override {
     TRACE_EVENT0("cc", "BitmapRasterBuffer::Playback");
     gfx::Rect playback_rect = raster_full_rect;
@@ -53,7 +53,7 @@
     size_t stride = 0u;
     RasterBufferProvider::PlaybackToMemory(
         lock_.sk_bitmap().getPixels(), resource_->format(), resource_->size(),
-        stride, raster_source, raster_full_rect, playback_rect, scale,
+        stride, raster_source, raster_full_rect, playback_rect, transform,
         lock_.color_space_for_raster(), playback_settings);
   }
 
diff --git a/cc/raster/gpu_raster_buffer_provider.cc b/cc/raster/gpu_raster_buffer_provider.cc
index 1cc36f18..de489ff 100644
--- a/cc/raster/gpu_raster_buffer_provider.cc
+++ b/cc/raster/gpu_raster_buffer_provider.cc
@@ -34,7 +34,7 @@
     const gfx::Size& resource_size,
     const gfx::Rect& raster_full_rect,
     const gfx::Rect& raster_dirty_rect,
-    float scale,
+    const gfx::AxisTransform2d& transform,
     const RasterSource::PlaybackSettings& playback_settings,
     ContextProvider* context_provider,
     ResourceProvider::ScopedWriteLockGL* resource_lock,
@@ -79,7 +79,7 @@
 
   raster_source->PlaybackToCanvas(
       sk_surface->getCanvas(), resource_lock->color_space_for_raster(),
-      raster_full_rect, playback_rect, scale, playback_settings);
+      raster_full_rect, playback_rect, transform, playback_settings);
 }
 
 }  // namespace
@@ -105,13 +105,13 @@
     const gfx::Rect& raster_full_rect,
     const gfx::Rect& raster_dirty_rect,
     uint64_t new_content_id,
-    float scale,
+    const gfx::AxisTransform2d& transform,
     const RasterSource::PlaybackSettings& playback_settings) {
   TRACE_EVENT0("cc", "GpuRasterBuffer::Playback");
   client_->PlaybackOnWorkerThread(&lock_, sync_token_,
                                   resource_has_previous_content_, raster_source,
                                   raster_full_rect, raster_dirty_rect,
-                                  new_content_id, scale, playback_settings);
+                                  new_content_id, transform, playback_settings);
 }
 
 GpuRasterBufferProvider::GpuRasterBufferProvider(
@@ -252,7 +252,7 @@
     const gfx::Rect& raster_full_rect,
     const gfx::Rect& raster_dirty_rect,
     uint64_t new_content_id,
-    float scale,
+    const gfx::AxisTransform2d& transform,
     const RasterSource::PlaybackSettings& playback_settings) {
   ContextProvider::ScopedContextLock scoped_context(worker_context_provider_);
   gpu::gles2::GLES2Interface* gl = scoped_context.ContextGL();
@@ -269,7 +269,7 @@
 
   RasterizeSource(raster_source, resource_has_previous_content,
                   resource_lock->size(), raster_full_rect, raster_dirty_rect,
-                  scale, playback_settings, worker_context_provider_,
+                  transform, playback_settings, worker_context_provider_,
                   resource_lock, async_worker_context_enabled_,
                   use_distance_field_text_, msaa_sample_count_);
 
diff --git a/cc/raster/gpu_raster_buffer_provider.h b/cc/raster/gpu_raster_buffer_provider.h
index 248a72f9..0777db2 100644
--- a/cc/raster/gpu_raster_buffer_provider.h
+++ b/cc/raster/gpu_raster_buffer_provider.h
@@ -51,7 +51,7 @@
       const gfx::Rect& raster_full_rect,
       const gfx::Rect& raster_dirty_rect,
       uint64_t new_content_id,
-      float scale,
+      const gfx::AxisTransform2d& transform,
       const RasterSource::PlaybackSettings& playback_settings);
 
  private:
@@ -70,7 +70,7 @@
         const gfx::Rect& raster_full_rect,
         const gfx::Rect& raster_dirty_rect,
         uint64_t new_content_id,
-        float scale,
+        const gfx::AxisTransform2d& transform,
         const RasterSource::PlaybackSettings& playback_settings) override;
 
     void set_sync_token(const gpu::SyncToken& sync_token) {
diff --git a/cc/raster/one_copy_raster_buffer_provider.cc b/cc/raster/one_copy_raster_buffer_provider.cc
index 214dfa9..abc63e46 100644
--- a/cc/raster/one_copy_raster_buffer_provider.cc
+++ b/cc/raster/one_copy_raster_buffer_provider.cc
@@ -57,12 +57,12 @@
     const gfx::Rect& raster_full_rect,
     const gfx::Rect& raster_dirty_rect,
     uint64_t new_content_id,
-    float scale,
+    const gfx::AxisTransform2d& transform,
     const RasterSource::PlaybackSettings& playback_settings) {
   TRACE_EVENT0("cc", "OneCopyRasterBuffer::Playback");
   client_->PlaybackAndCopyOnWorkerThread(
       resource_, &lock_, sync_token_, raster_source, raster_full_rect,
-      raster_dirty_rect, scale, playback_settings, previous_content_id_,
+      raster_dirty_rect, transform, playback_settings, previous_content_id_,
       new_content_id);
 }
 
@@ -215,7 +215,7 @@
     const RasterSource* raster_source,
     const gfx::Rect& raster_full_rect,
     const gfx::Rect& raster_dirty_rect,
-    float scale,
+    const gfx::AxisTransform2d& transform,
     const RasterSource::PlaybackSettings& playback_settings,
     uint64_t previous_content_id,
     uint64_t new_content_id) {
@@ -236,7 +236,7 @@
 
   PlaybackToStagingBuffer(
       staging_buffer.get(), resource, raster_source, raster_full_rect,
-      raster_dirty_rect, scale, resource_lock->color_space_for_raster(),
+      raster_dirty_rect, transform, resource_lock->color_space_for_raster(),
       playback_settings, previous_content_id, new_content_id);
 
   CopyOnWorkerThread(staging_buffer.get(), resource_lock, sync_token,
@@ -251,7 +251,7 @@
     const RasterSource* raster_source,
     const gfx::Rect& raster_full_rect,
     const gfx::Rect& raster_dirty_rect,
-    float scale,
+    const gfx::AxisTransform2d& transform,
     const gfx::ColorSpace& dst_color_space,
     const RasterSource::PlaybackSettings& playback_settings,
     uint64_t previous_content_id,
@@ -301,7 +301,7 @@
     RasterBufferProvider::PlaybackToMemory(
         buffer->memory(0), resource->format(), staging_buffer->size,
         buffer->stride(0), raster_source, raster_full_rect, playback_rect,
-        scale, dst_color_space, playback_settings);
+        transform, dst_color_space, playback_settings);
     buffer->Unmap();
     staging_buffer->content_id = new_content_id;
   }
diff --git a/cc/raster/one_copy_raster_buffer_provider.h b/cc/raster/one_copy_raster_buffer_provider.h
index f757e59..5d82849 100644
--- a/cc/raster/one_copy_raster_buffer_provider.h
+++ b/cc/raster/one_copy_raster_buffer_provider.h
@@ -56,7 +56,7 @@
       const RasterSource* raster_source,
       const gfx::Rect& raster_full_rect,
       const gfx::Rect& raster_dirty_rect,
-      float scale,
+      const gfx::AxisTransform2d& transform,
       const RasterSource::PlaybackSettings& playback_settings,
       uint64_t previous_content_id,
       uint64_t new_content_id);
@@ -77,7 +77,7 @@
         const gfx::Rect& raster_full_rect,
         const gfx::Rect& raster_dirty_rect,
         uint64_t new_content_id,
-        float scale,
+        const gfx::AxisTransform2d& transform,
         const RasterSource::PlaybackSettings& playback_settings) override;
 
     void set_sync_token(const gpu::SyncToken& sync_token) {
@@ -101,7 +101,7 @@
       const RasterSource* raster_source,
       const gfx::Rect& raster_full_rect,
       const gfx::Rect& raster_dirty_rect,
-      float scale,
+      const gfx::AxisTransform2d& transform,
       const gfx::ColorSpace& dst_color_space,
       const RasterSource::PlaybackSettings& playback_settings,
       uint64_t previous_content_id,
diff --git a/cc/raster/raster_buffer.h b/cc/raster/raster_buffer.h
index d045a70..0de76b3 100644
--- a/cc/raster/raster_buffer.h
+++ b/cc/raster/raster_buffer.h
@@ -11,6 +11,10 @@
 #include "cc/raster/raster_source.h"
 #include "ui/gfx/geometry/rect.h"
 
+namespace gfx {
+class AxisTransform2d;
+}  // namespace gfx
+
 namespace cc {
 
 class CC_EXPORT RasterBuffer {
@@ -23,7 +27,7 @@
       const gfx::Rect& raster_full_rect,
       const gfx::Rect& raster_dirty_rect,
       uint64_t new_content_id,
-      float scale,
+      const gfx::AxisTransform2d& transform,
       const RasterSource::PlaybackSettings& playback_settings) = 0;
 };
 
diff --git a/cc/raster/raster_buffer_provider.cc b/cc/raster/raster_buffer_provider.cc
index bb49ae0..fe76a0a 100644
--- a/cc/raster/raster_buffer_provider.cc
+++ b/cc/raster/raster_buffer_provider.cc
@@ -13,6 +13,7 @@
 #include "cc/resources/resource_format_utils.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkSurface.h"
+#include "ui/gfx/geometry/axis_transform2d.h"
 
 namespace cc {
 
@@ -52,7 +53,7 @@
     const RasterSource* raster_source,
     const gfx::Rect& canvas_bitmap_rect,
     const gfx::Rect& canvas_playback_rect,
-    float scale,
+    const gfx::AxisTransform2d& transform,
     const gfx::ColorSpace& target_color_space,
     const RasterSource::PlaybackSettings& playback_settings) {
   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
@@ -83,7 +84,7 @@
           SkSurface::MakeRasterDirect(info, memory, stride, &surface_props);
       raster_source->PlaybackToCanvas(surface->getCanvas(), target_color_space,
                                       canvas_bitmap_rect, canvas_playback_rect,
-                                      scale, playback_settings);
+                                      transform, playback_settings);
       return;
     }
     case RGBA_4444:
@@ -93,7 +94,7 @@
       // playback rect passed to PlaybackToCanvas. crbug.com/519070
       raster_source->PlaybackToCanvas(surface->getCanvas(), target_color_space,
                                       canvas_bitmap_rect, canvas_bitmap_rect,
-                                      scale, playback_settings);
+                                      transform, playback_settings);
 
       if (format == ETC1) {
         TRACE_EVENT0("cc",
diff --git a/cc/raster/raster_buffer_provider.h b/cc/raster/raster_buffer_provider.h
index 011cd1d..40611df 100644
--- a/cc/raster/raster_buffer_provider.h
+++ b/cc/raster/raster_buffer_provider.h
@@ -38,7 +38,7 @@
       const RasterSource* raster_source,
       const gfx::Rect& canvas_bitmap_rect,
       const gfx::Rect& canvas_playback_rect,
-      float scale,
+      const gfx::AxisTransform2d& transform,
       const gfx::ColorSpace& target_color_space,
       const RasterSource::PlaybackSettings& playback_settings);
 
diff --git a/cc/raster/raster_buffer_provider_unittest.cc b/cc/raster/raster_buffer_provider_unittest.cc
index 8e35a3d..ea4cdff 100644
--- a/cc/raster/raster_buffer_provider_unittest.cc
+++ b/cc/raster/raster_buffer_provider_unittest.cc
@@ -38,6 +38,7 @@
 #include "cc/tiles/tile_task_manager.h"
 #include "gpu/GLES2/gl2extchromium.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/axis_transform2d.h"
 
 namespace cc {
 namespace {
@@ -83,7 +84,8 @@
 
     uint64_t new_content_id = 0;
     raster_buffer_->Playback(raster_source_.get(), gfx::Rect(1, 1),
-                             gfx::Rect(1, 1), new_content_id, 1.f, settings);
+                             gfx::Rect(1, 1), new_content_id,
+                             gfx::AxisTransform2d(), settings);
   }
 
   // Overridden from TileTask:
diff --git a/cc/raster/raster_source.cc b/cc/raster/raster_source.cc
index 43d7792..796b7980 100644
--- a/cc/raster/raster_source.cc
+++ b/cc/raster/raster_source.cc
@@ -18,6 +18,7 @@
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkColorSpaceXformCanvas.h"
 #include "third_party/skia/include/core/SkPictureRecorder.h"
+#include "ui/gfx/geometry/axis_transform2d.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 
 namespace cc {
@@ -60,12 +61,13 @@
 
 RasterSource::~RasterSource() {}
 
-void RasterSource::PlaybackToCanvas(SkCanvas* raster_canvas,
-                                    const gfx::ColorSpace& canvas_color_space,
-                                    const gfx::Rect& canvas_bitmap_rect,
-                                    const gfx::Rect& canvas_playback_rect,
-                                    float raster_scale,
-                                    const PlaybackSettings& settings) const {
+void RasterSource::PlaybackToCanvas(
+    SkCanvas* raster_canvas,
+    const gfx::ColorSpace& canvas_color_space,
+    const gfx::Rect& canvas_bitmap_rect,
+    const gfx::Rect& canvas_playback_rect,
+    const gfx::AxisTransform2d& raster_transform,
+    const PlaybackSettings& settings) const {
   SkIRect raster_bounds = gfx::RectToSkIRect(canvas_bitmap_rect);
   if (!canvas_playback_rect.IsEmpty() &&
       !raster_bounds.intersect(gfx::RectToSkIRect(canvas_playback_rect)))
@@ -76,7 +78,9 @@
   raster_canvas->save();
   raster_canvas->translate(-canvas_bitmap_rect.x(), -canvas_bitmap_rect.y());
   raster_canvas->clipRect(SkRect::MakeFromIRect(raster_bounds));
-  raster_canvas->scale(raster_scale, raster_scale);
+  raster_canvas->translate(raster_transform.translation().x(),
+                           raster_transform.translation().y());
+  raster_canvas->scale(raster_transform.scale(), raster_transform.scale());
   PlaybackToCanvas(raster_canvas, canvas_color_space, settings);
   raster_canvas->restore();
 }
diff --git a/cc/raster/raster_source.h b/cc/raster/raster_source.h
index ff9d0f2..b6292c5 100644
--- a/cc/raster/raster_source.h
+++ b/cc/raster/raster_source.h
@@ -19,6 +19,10 @@
 #include "third_party/skia/include/core/SkPicture.h"
 #include "ui/gfx/color_space.h"
 
+namespace gfx {
+class AxisTransform2d;
+}  // namespace gfx
+
 namespace cc {
 class DisplayItemList;
 class DrawImage;
@@ -54,12 +58,20 @@
       const RecordingSource* other,
       bool can_use_lcd_text);
 
-  // TODO(trchen): Deprecated.
+  // Helper function to apply a few common operations before passing the canvas
+  // to the shorter version. This is useful for rastering into tiles.
+  // canvas is expected to be backed by a tile, with a default state.
+  // raster_transform will be applied to the display list, rastering the list
+  // into the "content space".
+  // canvas_bitmap_rect defines the extent of the tile in the content space,
+  // i.e. contents in the rect will be cropped and translated onto the canvas.
+  // canvas_playback_rect can be used to replay only part of the recording in,
+  // the content space, so only a sub-rect of the tile gets rastered.
   void PlaybackToCanvas(SkCanvas* canvas,
                         const gfx::ColorSpace& canvas_color_space,
                         const gfx::Rect& canvas_bitmap_rect,
                         const gfx::Rect& canvas_playback_rect,
-                        float contents_scale,
+                        const gfx::AxisTransform2d& raster_transform,
                         const PlaybackSettings& settings) const;
 
   // Raster this RasterSource into the given canvas. Canvas states such as
diff --git a/cc/raster/raster_source_unittest.cc b/cc/raster/raster_source_unittest.cc
index 4ec8c94..e1510781 100644
--- a/cc/raster/raster_source_unittest.cc
+++ b/cc/raster/raster_source_unittest.cc
@@ -15,6 +15,7 @@
 #include "third_party/skia/include/core/SkPixelRef.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
 #include "third_party/skia/include/core/SkShader.h"
+#include "ui/gfx/geometry/axis_transform2d.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size_conversions.h"
 
@@ -290,9 +291,10 @@
       SkCanvas canvas(bitmap);
       canvas.clear(SK_ColorTRANSPARENT);
 
-      raster->PlaybackToCanvas(&canvas, ColorSpaceForTesting(), canvas_rect,
-                               canvas_rect, contents_scale,
-                               RasterSource::PlaybackSettings());
+      raster->PlaybackToCanvas(
+          &canvas, ColorSpaceForTesting(), canvas_rect, canvas_rect,
+          gfx::AxisTransform2d(contents_scale, gfx::Vector2dF()),
+          RasterSource::PlaybackSettings());
 
       SkColor* pixels = reinterpret_cast<SkColor*>(bitmap.getPixels());
       int num_pixels = bitmap.width() * bitmap.height();
@@ -342,9 +344,10 @@
   // Playback the full rect which should make everything white.
   gfx::Rect raster_full_rect(content_bounds);
   gfx::Rect playback_rect(content_bounds);
-  raster->PlaybackToCanvas(&canvas, ColorSpaceForTesting(), raster_full_rect,
-                           playback_rect, contents_scale,
-                           RasterSource::PlaybackSettings());
+  raster->PlaybackToCanvas(
+      &canvas, ColorSpaceForTesting(), raster_full_rect, playback_rect,
+      gfx::AxisTransform2d(contents_scale, gfx::Vector2dF()),
+      RasterSource::PlaybackSettings());
 
   {
     SkColor* pixels = reinterpret_cast<SkColor*>(bitmap.getPixels());
@@ -374,9 +377,10 @@
   // We're going to playback from "everything is black" into a smaller area,
   // that touches the edge pixels of the recording.
   playback_rect.Inset(1, 2, 0, 1);
-  raster->PlaybackToCanvas(&canvas, ColorSpaceForTesting(), raster_full_rect,
-                           playback_rect, contents_scale,
-                           RasterSource::PlaybackSettings());
+  raster->PlaybackToCanvas(
+      &canvas, ColorSpaceForTesting(), raster_full_rect, playback_rect,
+      gfx::AxisTransform2d(contents_scale, gfx::Vector2dF()),
+      RasterSource::PlaybackSettings());
 
   SkColor* pixels = reinterpret_cast<SkColor*>(bitmap.getPixels());
   int num_black = 0;
@@ -439,9 +443,10 @@
   // Playback the full rect which should make everything light gray (alpha=10).
   gfx::Rect raster_full_rect(content_bounds);
   gfx::Rect playback_rect(content_bounds);
-  raster->PlaybackToCanvas(&canvas, ColorSpaceForTesting(), raster_full_rect,
-                           playback_rect, contents_scale,
-                           RasterSource::PlaybackSettings());
+  raster->PlaybackToCanvas(
+      &canvas, ColorSpaceForTesting(), raster_full_rect, playback_rect,
+      gfx::AxisTransform2d(contents_scale, gfx::Vector2dF()),
+      RasterSource::PlaybackSettings());
 
   {
     SkColor* pixels = reinterpret_cast<SkColor*>(bitmap.getPixels());
@@ -479,9 +484,10 @@
   // darker white background rectangle.
   playback_rect =
       gfx::Rect(gfx::ScaleToCeiledSize(partial_bounds, contents_scale));
-  raster->PlaybackToCanvas(&canvas, ColorSpaceForTesting(), raster_full_rect,
-                           playback_rect, contents_scale,
-                           RasterSource::PlaybackSettings());
+  raster->PlaybackToCanvas(
+      &canvas, ColorSpaceForTesting(), raster_full_rect, playback_rect,
+      gfx::AxisTransform2d(contents_scale, gfx::Vector2dF()),
+      RasterSource::PlaybackSettings());
 
   // Test that the whole playback_rect was cleared and repainted with new alpha.
   SkColor* pixels = reinterpret_cast<SkColor*>(bitmap.getPixels());
@@ -520,9 +526,10 @@
   bitmap.allocN32Pixels(canvas_rect.width(), canvas_rect.height());
   SkCanvas canvas(bitmap);
 
-  raster->PlaybackToCanvas(&canvas, ColorSpaceForTesting(), canvas_rect,
-                           canvas_rect, contents_scale,
-                           RasterSource::PlaybackSettings());
+  raster->PlaybackToCanvas(
+      &canvas, ColorSpaceForTesting(), canvas_rect, canvas_rect,
+      gfx::AxisTransform2d(contents_scale, gfx::Vector2dF()),
+      RasterSource::PlaybackSettings());
 
   SkColor* pixels = reinterpret_cast<SkColor*>(bitmap.getPixels());
   int num_pixels = bitmap.width() * bitmap.height();
@@ -595,8 +602,8 @@
   settings.playback_to_shared_canvas = true;
   settings.use_image_hijack_canvas = true;
   raster_source->PlaybackToCanvas(&canvas, ColorSpaceForTesting(),
-                                  gfx::Rect(size), gfx::Rect(size), 1.f,
-                                  settings);
+                                  gfx::Rect(size), gfx::Rect(size),
+                                  gfx::AxisTransform2d(), settings);
 
   EXPECT_EQ(SK_ColorGREEN, bitmap.getColor(0, 0));
   EXPECT_EQ(SK_ColorGREEN, bitmap.getColor(49, 0));
diff --git a/cc/raster/zero_copy_raster_buffer_provider.cc b/cc/raster/zero_copy_raster_buffer_provider.cc
index 27d6c88..85db2c46 100644
--- a/cc/raster/zero_copy_raster_buffer_provider.cc
+++ b/cc/raster/zero_copy_raster_buffer_provider.cc
@@ -34,7 +34,7 @@
       const gfx::Rect& raster_full_rect,
       const gfx::Rect& raster_dirty_rect,
       uint64_t new_content_id,
-      float scale,
+      const gfx::AxisTransform2d& transform,
       const RasterSource::PlaybackSettings& playback_settings) override {
     TRACE_EVENT0("cc", "ZeroCopyRasterBuffer::Playback");
     gfx::GpuMemoryBuffer* buffer = lock_.GetGpuMemoryBuffer();
@@ -52,7 +52,7 @@
     RasterBufferProvider::PlaybackToMemory(
         buffer->memory(0), resource_->format(), resource_->size(),
         buffer->stride(0), raster_source, raster_full_rect, raster_full_rect,
-        scale, lock_.color_space_for_raster(), playback_settings);
+        transform, lock_.color_space_for_raster(), playback_settings);
     buffer->Unmap();
   }
 
diff --git a/cc/tiles/tile_manager.cc b/cc/tiles/tile_manager.cc
index 9916918..bb8e450 100644
--- a/cc/tiles/tile_manager.cc
+++ b/cc/tiles/tile_manager.cc
@@ -29,6 +29,7 @@
 #include "cc/raster/task_category.h"
 #include "cc/tiles/frame_viewer_instrumentation.h"
 #include "cc/tiles/tile.h"
+#include "ui/gfx/geometry/axis_transform2d.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 
 namespace cc {
@@ -126,9 +127,10 @@
 
     DCHECK(raster_source_);
 
-    raster_buffer_->Playback(raster_source_.get(), content_rect_,
-                             invalid_content_rect_, new_content_id_,
-                             raster_scale_, playback_settings_);
+    raster_buffer_->Playback(
+        raster_source_.get(), content_rect_, invalid_content_rect_,
+        new_content_id_, gfx::AxisTransform2d(raster_scale_, gfx::Vector2dF()),
+        playback_settings_);
   }
 
   // Overridden from TileTask:
diff --git a/cc/tiles/tile_manager_unittest.cc b/cc/tiles/tile_manager_unittest.cc
index 39da63a0..3675241 100644
--- a/cc/tiles/tile_manager_unittest.cc
+++ b/cc/tiles/tile_manager_unittest.cc
@@ -1988,7 +1988,7 @@
         const gfx::Rect& raster_full_rect,
         const gfx::Rect& raster_dirty_rect,
         uint64_t new_content_id,
-        float scale,
+        const gfx::AxisTransform2d& transform,
         const RasterSource::PlaybackSettings& playback_settings) override {}
   };
 };
diff --git a/ui/gfx/BUILD.gn b/ui/gfx/BUILD.gn
index 77856da..a534b0e 100644
--- a/ui/gfx/BUILD.gn
+++ b/ui/gfx/BUILD.gn
@@ -626,6 +626,7 @@
       "color_utils_unittest.cc",
       "font_fallback_mac_unittest.cc",
       "font_list_unittest.cc",
+      "geometry/axis_transform2d_unittest.cc",
       "geometry/box_unittest.cc",
       "geometry/cubic_bezier_unittest.cc",
       "geometry/insets_unittest.cc",
diff --git a/ui/gfx/geometry/BUILD.gn b/ui/gfx/geometry/BUILD.gn
index 59690a2..0bf2db1 100644
--- a/ui/gfx/geometry/BUILD.gn
+++ b/ui/gfx/geometry/BUILD.gn
@@ -5,6 +5,8 @@
 component("geometry") {
   sources = [
     "../gfx_export.h",
+    "axis_transform2d.cc",
+    "axis_transform2d.h",
     "box_f.cc",
     "box_f.h",
     "cubic_bezier.cc",
diff --git a/ui/gfx/geometry/axis_transform2d.cc b/ui/gfx/geometry/axis_transform2d.cc
new file mode 100644
index 0000000..5b7d86a4
--- /dev/null
+++ b/ui/gfx/geometry/axis_transform2d.cc
@@ -0,0 +1,16 @@
+// Copyright 2013 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.
+
+#include "ui/gfx/geometry/axis_transform2d.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+std::string AxisTransform2d::ToString() const {
+  return base::StringPrintf("[%f, %s]", scale_,
+                            translation_.ToString().c_str());
+}
+
+}  // namespace gfx
\ No newline at end of file
diff --git a/ui/gfx/geometry/axis_transform2d.h b/ui/gfx/geometry/axis_transform2d.h
new file mode 100644
index 0000000..1829bf6
--- /dev/null
+++ b/ui/gfx/geometry/axis_transform2d.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2017 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.
+
+#ifndef UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_
+#define UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_
+
+#include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+// This class implements the subset of 2D linear transforms that only
+// translation and uniform scaling are allowed.
+// Internally this is stored as a scalar pre-scale factor, and a vector
+// for post-translation. The class constructor and member accessor follows
+// the same convention.
+class GFX_EXPORT AxisTransform2d {
+ public:
+  constexpr AxisTransform2d() = default;
+  constexpr AxisTransform2d(float scale, const Vector2dF& translation)
+      : scale_(scale), translation_(translation) {}
+
+  bool operator==(const AxisTransform2d& other) const {
+    return scale_ == other.scale_ && translation_ == other.translation_;
+  }
+  bool operator!=(const AxisTransform2d& other) const {
+    return !(*this == other);
+  }
+
+  void PreScale(float scale) { scale_ *= scale; }
+  void PostScale(float scale) {
+    scale_ *= scale;
+    translation_.Scale(scale);
+  }
+  void PreTranslate(const Vector2dF& translation) {
+    translation_ += ScaleVector2d(translation, scale_);
+  }
+  void PostTranslate(const Vector2dF& translation) {
+    translation_ += translation;
+  }
+
+  void PreConcat(const AxisTransform2d& pre) {
+    PreTranslate(pre.translation_);
+    PreScale(pre.scale_);
+  }
+  void PostConcat(const AxisTransform2d& post) {
+    PostScale(post.scale_);
+    PostTranslate(post.translation_);
+  }
+
+  void Invert() {
+    DCHECK(scale_);
+    scale_ = 1.f / scale_;
+    translation_.Scale(-scale_);
+  }
+
+  PointF MapPoint(const PointF& p) const {
+    return ScalePoint(p, scale_) + translation_;
+  }
+  PointF InverseMapPoint(const PointF& p) const {
+    return ScalePoint(p - translation_, 1.f / scale_);
+  }
+
+  RectF MapRect(const RectF& r) const {
+    DCHECK(scale_ >= 0.f);
+    return ScaleRect(r, scale_) + translation_;
+  }
+  RectF InverseMapRect(const RectF& r) const {
+    DCHECK(scale_ > 0.f);
+    return ScaleRect(r - translation_, 1.f / scale_);
+  }
+
+  float scale() const { return scale_; }
+  const Vector2dF& translation() const { return translation_; }
+
+  std::string ToString() const;
+
+ private:
+  // Scale is applied before translation, i.e.
+  // this->Transform(p) == scale_ * p + translation_
+  float scale_ = 1.f;
+  Vector2dF translation_;
+};
+
+static inline AxisTransform2d PreScaleAxisTransform2d(const AxisTransform2d& t,
+                                                      float scale) {
+  AxisTransform2d result(t);
+  result.PreScale(scale);
+  return result;
+}
+
+static inline AxisTransform2d PostScaleAxisTransform2d(const AxisTransform2d& t,
+                                                       float scale) {
+  AxisTransform2d result(t);
+  result.PostScale(scale);
+  return result;
+}
+
+static inline AxisTransform2d PreTranslateAxisTransform2d(
+    const AxisTransform2d& t,
+    const Vector2dF& translation) {
+  AxisTransform2d result(t);
+  result.PreTranslate(translation);
+  return result;
+}
+
+static inline AxisTransform2d PostTranslateAxisTransform2d(
+    const AxisTransform2d& t,
+    const Vector2dF& translation) {
+  AxisTransform2d result(t);
+  result.PostTranslate(translation);
+  return result;
+}
+
+static inline AxisTransform2d ConcatAxisTransform2d(
+    const AxisTransform2d& post,
+    const AxisTransform2d& pre) {
+  AxisTransform2d result(post);
+  result.PreConcat(pre);
+  return result;
+}
+
+static inline AxisTransform2d InvertAxisTransform2d(const AxisTransform2d& t) {
+  AxisTransform2d result = t;
+  result.Invert();
+  return result;
+}
+
+// This is declared here for use in gtest-based unit tests but is defined in
+// the //ui/gfx:test_support target. Depend on that to use this in your unit
+// test. This should not be used in production code - call ToString() instead.
+void PrintTo(const AxisTransform2d&, ::std::ostream* os);
+
+}  // namespace gfx
+
+#endif  // UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_
diff --git a/ui/gfx/geometry/axis_transform2d_unittest.cc b/ui/gfx/geometry/axis_transform2d_unittest.cc
new file mode 100644
index 0000000..b132c69
--- /dev/null
+++ b/ui/gfx/geometry/axis_transform2d_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright 2017 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.
+
+#include "ui/gfx/geometry/axis_transform2d.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/test/gfx_util.h"
+
+namespace gfx {
+namespace {
+
+TEST(AxisTransform2dTest, Mapping) {
+  AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+
+  PointF p(150.f, 100.f);
+  EXPECT_EQ(PointF(191.25f, 180.f), t.MapPoint(p));
+  EXPECT_POINTF_EQ(PointF(117.f, 36.f), t.InverseMapPoint(p));
+
+  RectF r(150.f, 100.f, 22.5f, 37.5f);
+  EXPECT_EQ(RectF(191.25f, 180.f, 28.125f, 46.875f), t.MapRect(r));
+  EXPECT_RECTF_EQ(RectF(117.f, 36.f, 18.f, 30.f), t.InverseMapRect(r));
+}
+
+TEST(AxisTransform2dTest, Scaling) {
+  {
+    AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+    EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(3.75f, 55.f)),
+              PreScaleAxisTransform2d(t, 1.25));
+    t.PreScale(1.25);
+    EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(3.75f, 55.f)), t);
+  }
+
+  {
+    AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+    EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(4.6875f, 68.75f)),
+              PostScaleAxisTransform2d(t, 1.25));
+    t.PostScale(1.25);
+    EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(4.6875f, 68.75f)), t);
+  }
+}
+
+TEST(AxisTransform2dTest, Translating) {
+  Vector2dF tr(3.f, -5.f);
+  {
+    AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+    EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(7.5f, 48.75f)),
+              PreTranslateAxisTransform2d(t, tr));
+    t.PreTranslate(tr);
+    EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(7.5f, 48.75f)), t);
+  }
+
+  {
+    AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+    EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(6.75f, 50.f)),
+              PostTranslateAxisTransform2d(t, tr));
+    t.PostTranslate(tr);
+    EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(6.75f, 50.f)), t);
+  }
+}
+
+TEST(AxisTransform2dTest, Concat) {
+  AxisTransform2d pre(1.25f, Vector2dF(3.75f, 55.f));
+  AxisTransform2d post(0.5f, Vector2dF(10.f, 5.f));
+  AxisTransform2d expectation(0.625f, Vector2dF(11.875f, 32.5f));
+  EXPECT_EQ(expectation, ConcatAxisTransform2d(post, pre));
+
+  AxisTransform2d post_concat = pre;
+  post_concat.PostConcat(post);
+  EXPECT_EQ(expectation, post_concat);
+
+  AxisTransform2d pre_concat = post;
+  pre_concat.PreConcat(pre);
+  EXPECT_EQ(expectation, pre_concat);
+}
+
+TEST(AxisTransform2dTest, Inverse) {
+  AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+  AxisTransform2d inv_inplace = t;
+  inv_inplace.Invert();
+  AxisTransform2d inv_out_of_place = InvertAxisTransform2d(t);
+
+  EXPECT_AXIS_TRANSFORM2D_EQ(inv_inplace, inv_out_of_place);
+  EXPECT_AXIS_TRANSFORM2D_EQ(AxisTransform2d(),
+                             ConcatAxisTransform2d(t, inv_inplace));
+  EXPECT_AXIS_TRANSFORM2D_EQ(AxisTransform2d(),
+                             ConcatAxisTransform2d(inv_inplace, t));
+}
+
+}  // namespace
+}  // namespace gfx
diff --git a/ui/gfx/test/gfx_util.cc b/ui/gfx/test/gfx_util.cc
index d8bf5f2a..e70b960 100644
--- a/ui/gfx/test/gfx_util.cc
+++ b/ui/gfx/test/gfx_util.cc
@@ -8,6 +8,7 @@
 #include <sstream>
 #include <string>
 
+#include "ui/gfx/geometry/axis_transform2d.h"
 #include "ui/gfx/geometry/box_f.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/point3_f.h"
@@ -45,6 +46,21 @@
 
 }  // namespace
 
+::testing::AssertionResult AssertAxisTransform2dFloatEqual(
+    const char* lhs_expr,
+    const char* rhs_expr,
+    const AxisTransform2d& lhs,
+    const AxisTransform2d& rhs) {
+  if (FloatAlmostEqual(lhs.scale(), rhs.scale()) &&
+      FloatAlmostEqual(lhs.translation().x(), rhs.translation().x()) &&
+      FloatAlmostEqual(lhs.translation().y(), rhs.translation().y())) {
+    return ::testing::AssertionSuccess();
+  }
+  return ::testing::AssertionFailure()
+         << "Value of: " << rhs_expr << "\n  Actual: " << rhs.ToString()
+         << "\nExpected: " << lhs_expr << "\nWhich is: " << lhs.ToString();
+}
+
 ::testing::AssertionResult AssertBoxFloatEqual(const char* lhs_expr,
                                                const char* rhs_expr,
                                                const BoxF& lhs,
@@ -63,6 +79,19 @@
                                        << "\nWhich is: " << lhs.ToString();
 }
 
+::testing::AssertionResult AssertPointFloatEqual(const char* lhs_expr,
+                                                 const char* rhs_expr,
+                                                 const PointF& lhs,
+                                                 const PointF& rhs) {
+  if (FloatAlmostEqual(lhs.x(), rhs.x()) &&
+      FloatAlmostEqual(lhs.y(), rhs.y())) {
+    return ::testing::AssertionSuccess();
+  }
+  return ::testing::AssertionFailure()
+         << "Value of: " << rhs_expr << "\n  Actual: " << rhs.ToString()
+         << "\nExpected: " << lhs_expr << "\nWhich is: " << lhs.ToString();
+}
+
 ::testing::AssertionResult AssertRectFloatEqual(const char* lhs_expr,
                                                 const char* rhs_expr,
                                                 const RectF& lhs,
@@ -91,6 +120,10 @@
                                        << "\nWhich is: " << ColorAsString(lhs);
 }
 
+void PrintTo(const AxisTransform2d& transform, ::std::ostream* os) {
+  *os << transform.ToString();
+}
+
 void PrintTo(const BoxF& box, ::std::ostream* os) {
   *os << box.ToString();
 }
diff --git a/ui/gfx/test/gfx_util.h b/ui/gfx/test/gfx_util.h
index 1401d5c..e7ed59d 100644
--- a/ui/gfx/test/gfx_util.h
+++ b/ui/gfx/test/gfx_util.h
@@ -10,12 +10,23 @@
 
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkColor.h"
-#include "ui/gfx/geometry/box_f.h"
-#include "ui/gfx/geometry/rect_f.h"
 
 namespace gfx {
 
-// Checks that the box coordinates are each almost equal floats.
+class AxisTransform2d;
+class BoxF;
+class PointF;
+class RectF;
+
+#define EXPECT_AXIS_TRANSFORM2D_EQ(a, b) \
+  EXPECT_PRED_FORMAT2(::gfx::AssertAxisTransform2dFloatEqual, a, b)
+
+::testing::AssertionResult AssertAxisTransform2dFloatEqual(
+    const char* lhs_expr,
+    const char* rhs_expr,
+    const AxisTransform2d& lhs,
+    const AxisTransform2d& rhs);
+
 #define EXPECT_BOXF_EQ(a, b) \
   EXPECT_PRED_FORMAT2(::gfx::AssertBoxFloatEqual, a, b)
 
@@ -24,6 +35,14 @@
                                                const BoxF& lhs,
                                                const BoxF& rhs);
 
+#define EXPECT_POINTF_EQ(a, b) \
+  EXPECT_PRED_FORMAT2(::gfx::AssertPointFloatEqual, a, b)
+
+::testing::AssertionResult AssertPointFloatEqual(const char* lhs_expr,
+                                                 const char* rhs_expr,
+                                                 const PointF& lhs,
+                                                 const PointF& rhs);
+
 #define EXPECT_RECTF_EQ(a, b) \
   EXPECT_PRED_FORMAT2(::gfx::AssertRectFloatEqual, a, b)