[scenic] adding YUY2 to image pipe

We copy from host memory to GPU memory right now anyway
so this adds in a conversion step from YUY2 to BGRA if
the Image_Info has that format set.

Change-Id: I4c96f4a2d819da11ce0ba04ffc2fc332e8ec79c8
diff --git a/lib/ui/scenic/resources/gpu_image.cc b/lib/ui/scenic/resources/gpu_image.cc
index e59a530..b239432 100644
--- a/lib/ui/scenic/resources/gpu_image.cc
+++ b/lib/ui/scenic/resources/gpu_image.cc
@@ -43,6 +43,10 @@
       bytes_per_pixel = 4u;
       pixel_alignment = 4u;
       break;
+    case scenic::ImageInfo::PixelFormat::YUY2:
+      error_reporter->ERROR()
+          << "GpuImage::CreateFromMemory(): PixelFormat must be BGRA_8.";
+      return nullptr;
   }
 
   if (image_info->width <= 0) {
diff --git a/lib/ui/scenic/resources/host_image.cc b/lib/ui/scenic/resources/host_image.cc
index b08d59e..3ddeac1 100644
--- a/lib/ui/scenic/resources/host_image.cc
+++ b/lib/ui/scenic/resources/host_image.cc
@@ -8,6 +8,7 @@
 #include "garnet/lib/ui/scenic/resources/gpu_memory.h"
 #include "garnet/lib/ui/scenic/resources/host_memory.h"
 #include "lib/escher/util/image_utils.h"
+#include "lib/escher/util/image_formats.h"
 
 namespace scene_manager {
 
@@ -18,74 +19,74 @@
 HostImage::HostImage(Session* session,
                      scenic::ResourceId id,
                      HostMemoryPtr memory,
-                     escher::ImagePtr image,
-                     uint64_t host_memory_offset)
+                     escher::ImagePtr gpu_image,
+                     uint64_t host_memory_offset,
+                     scenic::ImageInfo host_image_format)
     : Image(session, id, HostImage::kTypeInfo),
       memory_(std::move(memory)),
-      memory_offset_(host_memory_offset) {
-  image_ = std::move(image);
+      memory_offset_(host_memory_offset),
+      host_image_format_(host_image_format) {
+  image_ = std::move(gpu_image);
+  image_conversion_function_ =
+      escher::image_formats::GetConversionFunction(host_image_format);
 }
 
 ImagePtr HostImage::New(Session* session,
                         scenic::ResourceId id,
                         HostMemoryPtr host_memory,
-                        const scenic::ImageInfoPtr& image_info,
+                        const scenic::ImageInfoPtr& host_image_info,
                         uint64_t memory_offset,
                         mz::ErrorReporter* error_reporter) {
-  vk::Format pixel_format = vk::Format::eUndefined;
-  size_t bytes_per_pixel;
-  size_t pixel_alignment;
-  switch (image_info->pixel_format) {
-    case scenic::ImageInfo::PixelFormat::BGRA_8:
-      pixel_format = vk::Format::eB8G8R8A8Unorm;
-      bytes_per_pixel = 4u;
-      pixel_alignment = 4u;
-      break;
-  }
+  // No matter what the incoming format, the gpu format will be BGRA:
+  vk::Format gpu_image_pixel_format = vk::Format::eB8G8R8A8Unorm;
+  size_t bytes_per_pixel =
+      escher::image_formats::BytesPerPixel(host_image_info->pixel_format);
+  size_t pixel_alignment =
+      escher::image_formats::PixelAlignment(host_image_info->pixel_format);
 
-  if (image_info->width <= 0) {
+  if (host_image_info->width <= 0) {
     error_reporter->ERROR()
         << "Image::CreateFromMemory(): width must be greater than 0.";
     return nullptr;
   }
-  if (image_info->height <= 0) {
+  if (host_image_info->height <= 0) {
     error_reporter->ERROR()
         << "Image::CreateFromMemory(): height must be greater than 0.";
     return nullptr;
   }
 
   auto& caps = session->engine()->escher()->device()->caps();
-  if (image_info->width > caps.max_image_width) {
+  if (host_image_info->width > caps.max_image_width) {
     error_reporter->ERROR()
         << "Image::CreateFromMemory(): image width exceeds maximum ("
-        << image_info->width << " vs. " << caps.max_image_width << ").";
+        << host_image_info->width << " vs. " << caps.max_image_width << ").";
     return nullptr;
   }
-  if (image_info->height > caps.max_image_height) {
+  if (host_image_info->height > caps.max_image_height) {
     error_reporter->ERROR()
         << "Image::CreateFromMemory(): image height exceeds maximum ("
-        << image_info->height << " vs. " << caps.max_image_height << ").";
+        << host_image_info->height << " vs. " << caps.max_image_height << ").";
     return nullptr;
   }
 
-  if (image_info->stride < image_info->width * bytes_per_pixel) {
+  if (host_image_info->stride < host_image_info->width * bytes_per_pixel) {
     error_reporter->ERROR()
         << "Image::CreateFromMemory(): stride too small for width.";
     return nullptr;
   }
-  if (image_info->stride % pixel_alignment != 0) {
+  if (host_image_info->stride % pixel_alignment != 0) {
     error_reporter->ERROR()
         << "Image::CreateFromMemory(): stride must preserve pixel alignment.";
     return nullptr;
   }
-  if (image_info->tiling != scenic::ImageInfo::Tiling::LINEAR) {
+  if (host_image_info->tiling != scenic::ImageInfo::Tiling::LINEAR) {
     error_reporter->ERROR()
         << "Image::CreateFromMemory(): tiling must be LINEAR for images "
         << "created using host memory.";
     return nullptr;
   }
 
-  size_t image_size = image_info->height * image_info->stride;
+  size_t image_size = host_image_info->height * host_image_info->stride;
   if (memory_offset >= host_memory->size()) {
     error_reporter->ERROR()
         << "Image::CreateFromMemory(): the offset of the Image must be "
@@ -101,27 +102,27 @@
   }
 
   // TODO(MZ-141): Support non-minimal strides.
-  if (image_info->stride != image_info->width * bytes_per_pixel) {
+  if (host_image_info->stride != host_image_info->width * bytes_per_pixel) {
     error_reporter->ERROR()
         << "Image::CreateFromMemory(): the stride must be minimal (MZ-141)";
     return nullptr;
   }
 
-  auto escher_image = escher::image_utils::NewImageFromPixels(
-      session->engine()->escher_image_factory(),
-      session->engine()->escher_gpu_uploader(),
-      static_cast<uint8_t*>(host_memory->memory_base()) + memory_offset,
-      pixel_format, image_info->width, image_info->height);
+  auto escher_image = escher::image_utils::NewGpuImageFromPixels(
+      session->engine()->escher_image_factory(), gpu_image_pixel_format,
+      host_image_info->width, host_image_info->height);
 
   return fxl::AdoptRef(new HostImage(session, id, std::move(host_memory),
-                                     std::move(escher_image), memory_offset));
+                                     std::move(escher_image), memory_offset,
+                                     *host_image_info));
 }
 
 bool HostImage::UpdatePixels() {
   if (session()->engine()->escher_gpu_uploader()) {
     escher::image_utils::WritePixelsToImage(
         session()->engine()->escher_gpu_uploader(),
-        static_cast<uint8_t*>(memory_->memory_base()) + memory_offset_, image_);
+        static_cast<uint8_t*>(memory_->memory_base()) + memory_offset_, image_,
+        image_conversion_function_);
     return true;
   }
   return false;
@@ -134,8 +135,10 @@
   escher::ImagePtr escher_image = escher::Image::New(
       image_owner, escher::ImageInfo(), vk::Image(), nullptr);
   FXL_CHECK(escher_image);
-  return fxl::AdoptRef(
-      new HostImage(session, id, host_memory, escher_image, 0));
+  scenic::ImageInfo host_image_format;
+  host_image_format.pixel_format = scenic::ImageInfo::PixelFormat::BGRA_8;
+  return fxl::AdoptRef(new HostImage(session, id, host_memory, escher_image, 0,
+                                     host_image_format));
 }
 
 }  // namespace scene_manager
diff --git a/lib/ui/scenic/resources/host_image.h b/lib/ui/scenic/resources/host_image.h
index 4be8111..3568980 100644
--- a/lib/ui/scenic/resources/host_image.h
+++ b/lib/ui/scenic/resources/host_image.h
@@ -15,6 +15,7 @@
 
 class HostImage;
 using HostImagePtr = fxl::RefPtr<Image>;
+using ImgConvertFunc = fbl::Function<void(void*, void*, uint32_t, uint32_t)>;
 
 // An Image whose contents come from host-accessible memory.
 class HostImage : public Image {
@@ -37,7 +38,7 @@
   static ImagePtr New(Session* session,
                       scenic::ResourceId id,
                       HostMemoryPtr memory,
-                      const scenic::ImageInfoPtr& image_info,
+                      const scenic::ImageInfoPtr& host_image_info,
                       uint64_t memory_offset,
                       mz::ErrorReporter* error_reporter);
 
@@ -57,18 +58,22 @@
   // |session| is the Session that this image can be referenced from.
   // |id| is the ID assigned to the resource.
   // |memory| is the host memory that is associated with this image.
-  // |image| is the escher::Image that is being wrapped.
+  // |gpu_image| is the escher::Image that is being wrapped.
   // |host_memory_offset| specifies the offset into |memory| where the image is
   // stored.
   HostImage(Session* session,
             scenic::ResourceId id,
             HostMemoryPtr memory,
-            escher::ImagePtr image,
-            uint64_t host_memory_offset);
+            escher::ImagePtr gpu_image,
+            uint64_t host_memory_offset,
+            scenic::ImageInfo host_image_format);
 
   HostMemoryPtr memory_;
   // The offset into |memory_| where the image is stored, in bytes.
   uint64_t memory_offset_;
+  // The format of the image stored in host memory
+  scenic::ImageInfo host_image_format_;
+  ImgConvertFunc image_conversion_function_ = nullptr;
 };
 
 }  // namespace scene_manager
diff --git a/public/lib/escher/BUILD.gn b/public/lib/escher/BUILD.gn
index 4d2a469..c658431 100644
--- a/public/lib/escher/BUILD.gn
+++ b/public/lib/escher/BUILD.gn
@@ -229,6 +229,8 @@
     "util/align.h",
     "util/depth_to_color.cc",
     "util/depth_to_color.h",
+    "util/image_formats.cc",
+    "util/image_formats.h",
     "util/image_utils.cc",
     "util/image_utils.h",
     "util/stopwatch.h",
diff --git a/public/lib/escher/util/image_formats.cc b/public/lib/escher/util/image_formats.cc
new file mode 100644
index 0000000..53464c5
--- /dev/null
+++ b/public/lib/escher/util/image_formats.cc
@@ -0,0 +1,140 @@
+
+#include "lib/escher/util/image_formats.h"
+
+namespace escher {
+namespace image_formats {
+
+namespace {
+
+uint8_t clip(int in) {
+  uint32_t out = in < 0 ? 0 : (uint32_t)in;
+  return out > 255 ? 255 : (out & 0xff);
+}
+
+// Takes 4 bytes of YUY2 and writes 8 bytes of RGBA
+// TODO(garratt): do this better with a lookup table
+void Yuy2ToBgra(uint8_t* yuy2, uint8_t* bgra1, uint8_t* bgra2) {
+  int u = yuy2[1] - 128;
+  int y1 = 298 * (yuy2[0] - 16);
+  int v = yuy2[3] - 128;
+  int y2 = 298 * (yuy2[2] - 16);
+  bgra1[0] = clip(((y1 + 516 * u + 128) / 256));            // blue
+  bgra1[1] = clip(((y1 - 208 * v - 100 * u + 128) / 256));  // green
+  bgra1[2] = clip(((y1 + 409 * v + 128) / 256));            // red
+  bgra1[3] = 0xff;                                          // alpha
+
+  bgra2[0] = clip(((y2 + 516 * u + 128) / 256));            // blue
+  bgra2[1] = clip(((y2 - 208 * v - 100 * u + 128) / 256));  // green
+  bgra2[2] = clip(((y2 + 409 * v + 128) / 256));            // red
+  bgra2[3] = 0xff;                                          // alpha
+}
+
+void ConvertYuy2ToBgra(uint8_t* out_ptr,
+                       uint8_t* in_ptr,
+                       uint64_t buffer_size) {
+  // converts to BGRA
+  // uint8_t addresses:
+  //   0   1   2   3   4   5   6   7   8
+  // | Y | U | Y | V |
+  // | B | G | R | A | B | G | R | A
+  // We have 2 bytes per pixel, but we need to convert blocks of 4:
+  uint32_t num_double_pixels = buffer_size / 4;
+  // Since in_ptr and out_ptr are uint8_t, we step by 4 (bytes)
+  // in the incoming buffer, and 8 (bytes) in the  output buffer.
+  for (unsigned int i = 0; i < num_double_pixels; i++) {
+    Yuy2ToBgra(&in_ptr[4 * i], &out_ptr[8 * i], &out_ptr[8 * i + 4]);
+  }
+}
+
+void ConvertYuy2ToBgraAndMirror(uint8_t* out_ptr,
+                                uint8_t* in_ptr,
+                                uint32_t out_width,
+                                uint32_t out_height) {
+  uint32_t double_pixels_per_row = out_width / 2;
+  uint32_t in_stride = out_width * 2;
+  uint32_t out_stride = out_width * 4;
+  // converts to BGRA and mirrors left-right
+  for (uint32_t y = 0; y < out_height; ++y)
+    for (uint32_t x = 0; x < double_pixels_per_row; ++x) {
+      uint64_t out = 8 * ((double_pixels_per_row - 1 - x)) + y * out_stride;
+      Yuy2ToBgra(&in_ptr[4 * x + y * in_stride], &out_ptr[out + 4],
+                 &out_ptr[out]);
+    }
+}
+
+void MirrorBgra(uint32_t* out_ptr,
+                uint32_t* in_ptr,
+                uint32_t width,
+                uint32_t height) {
+  // converts to BGRA and mirrors left-right
+  for (uint32_t y = 0; y < height; ++y)
+    for (uint32_t x = 0; x < width; ++x) {
+      uint64_t out = ((width - 1 - x)) + y * width;
+      out_ptr[out] = in_ptr[x + y * width];
+    }
+}
+
+}  // anonymous namespace
+
+size_t BytesPerPixel(const scenic::ImageInfo::PixelFormat& pixel_format) {
+  switch (pixel_format) {
+    case scenic::ImageInfo::PixelFormat::BGRA_8:
+      return 4u;
+    case scenic::ImageInfo::PixelFormat::YUY2:
+      return 2u;
+  }
+  // TODO(garratt): throw fatal error. All formats should be enumerated here
+  return 0;
+}
+
+size_t PixelAlignment(const scenic::ImageInfo::PixelFormat& pixel_format) {
+  switch (pixel_format) {
+    case scenic::ImageInfo::PixelFormat::BGRA_8:
+      return 4u;
+    case scenic::ImageInfo::PixelFormat::YUY2:
+      return 2u;
+  }
+  // TODO(garratt): throw fatal error. All formats should be enumerated here
+  return 0;
+}
+
+ImgConvertFunc GetConversionFunction(const scenic::ImageInfo& image_info) {
+  size_t bpp = BytesPerPixel(image_info.pixel_format);
+  switch (image_info.pixel_format) {
+    case scenic::ImageInfo::PixelFormat::BGRA_8:
+      if (image_info.transform ==
+          scenic::ImageInfo::Transform::FLIP_HORIZONTAL) {
+        return [](void* out, void* in, uint32_t width, uint32_t height) {
+          MirrorBgra(reinterpret_cast<uint32_t*>(out),
+                     reinterpret_cast<uint32_t*>(in), width, height);
+        };
+      } else {
+        // no conversion needed.
+        return [bpp](void* out, void* in, uint32_t width, uint32_t height) {
+          memcpy(out, in, width * height * bpp);
+        };
+      }
+      break;
+    // TODO(garratt): support vertical flipping
+    case scenic::ImageInfo::PixelFormat::YUY2:
+      if (image_info.transform ==
+          scenic::ImageInfo::Transform::FLIP_HORIZONTAL) {
+        return [](void* out, void* in, uint32_t width, uint32_t height) {
+          ConvertYuy2ToBgraAndMirror(reinterpret_cast<uint8_t*>(out),
+                                     reinterpret_cast<uint8_t*>(in), width,
+                                     height);
+        };
+      } else {
+        return [bpp](void* out, void* in, uint32_t width, uint32_t height) {
+          ConvertYuy2ToBgra(reinterpret_cast<uint8_t*>(out),
+                            reinterpret_cast<uint8_t*>(in),
+                            width * height * bpp);
+        };
+      }
+      break;
+  }
+  return nullptr;
+}
+
+}  // namespace image_formats
+}  // namespace escher
diff --git a/public/lib/escher/util/image_formats.h b/public/lib/escher/util/image_formats.h
new file mode 100644
index 0000000..558f837
--- /dev/null
+++ b/public/lib/escher/util/image_formats.h
@@ -0,0 +1,30 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include "lib/images/fidl/image_info.fidl.h"
+
+// Contains utilities for converting from various formats to BGRA_8, which is
+// what is needed to render.
+// TODO(garratt): Merge with existing image conversion libraries in media:
+// bin/media/video/video_converter.h
+
+namespace escher {
+namespace image_formats {
+
+// Returns the number of bytes per pixel for the given format.
+size_t BytesPerPixel(const scenic::ImageInfo::PixelFormat& pixel_format);
+
+// Returns the pixel alignment for the given format.
+size_t PixelAlignment(const scenic::ImageInfo::PixelFormat& pixel_format);
+
+using ImgConvertFunc = fbl::Function<void(void*, void*, uint32_t, uint32_t)>;
+
+// Returns a function that can be used to convert any format supported in
+// ImageInfo into a BGRA_8 image.
+ImgConvertFunc GetConversionFunction(const scenic::ImageInfo& image_info);
+
+}  // namespace image_formats
+}  // namespace escher
diff --git a/public/lib/escher/util/image_utils.cc b/public/lib/escher/util/image_utils.cc
index d9cb16f..e58fea8 100644
--- a/public/lib/escher/util/image_utils.cc
+++ b/public/lib/escher/util/image_utils.cc
@@ -10,6 +10,7 @@
 #include "lib/escher/impl/vulkan_utils.h"
 #include "lib/escher/vk/gpu_mem.h"
 #include "lib/escher/vk/image_factory.h"
+#include "lib/escher/util/image_formats.h"
 
 namespace {
 struct RGBA {
@@ -99,15 +100,12 @@
   return image_factory->NewImage(info);
 }
 
-ImagePtr NewImageFromPixels(ImageFactory* image_factory,
-                            impl::GpuUploader* gpu_uploader,
-                            uint8_t* pixels,
-                            vk::Format format,
-                            uint32_t width,
-                            uint32_t height,
-                            vk::ImageUsageFlags additional_flags) {
+ImagePtr NewGpuImageFromPixels(ImageFactory* image_factory,
+                               vk::Format format,
+                               uint32_t width,
+                               uint32_t height,
+                               vk::ImageUsageFlags additional_flags) {
   FXL_DCHECK(image_factory);
-  FXL_DCHECK(gpu_uploader);
 
   ImageInfo info;
   info.format = format;
@@ -118,26 +116,30 @@
                vk::ImageUsageFlagBits::eSampled;
 
   // Create the new image.
-  auto image = image_factory->NewImage(info);
+  auto gpu_image = image_factory->NewImage(info);
 
-  WritePixelsToImage(gpu_uploader, pixels, image);
-
-  return image;
+  return gpu_image;
 }
 
-void WritePixelsToImage(impl::GpuUploader* gpu_uploader,
-                        uint8_t* pixels,
-                        ImagePtr image) {
+void WritePixelsToImage(
+    impl::GpuUploader* gpu_uploader,
+    uint8_t* pixels,
+    ImagePtr gpu_image,
+    const escher::image_formats::ImgConvertFunc& conversion_func) {
   FXL_DCHECK(gpu_uploader);
-  FXL_DCHECK(image);
+  FXL_DCHECK(gpu_image);
   FXL_DCHECK(pixels);
 
-  size_t bytes_per_pixel = BytesPerPixel(image->info().format);
-  size_t width = image->info().width;
-  size_t height = image->info().height;
+  size_t bytes_per_pixel = BytesPerPixel(gpu_image->info().format);
+  size_t width = gpu_image->info().width;
+  size_t height = gpu_image->info().height;
 
   auto writer = gpu_uploader->GetWriter(width * height * bytes_per_pixel);
-  memcpy(writer.ptr(), pixels, width * height * bytes_per_pixel);
+  if (!conversion_func) {
+    std::memcpy(writer.ptr(), pixels, width * height * bytes_per_pixel);
+  } else {
+    conversion_func(writer.ptr(), pixels, width, height);
+  }
 
   vk::BufferImageCopy region;
   region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
@@ -149,7 +151,7 @@
   region.imageExtent.depth = 1;
   region.bufferOffset = 0;
 
-  writer.WriteImage(image, region, Semaphore::New(gpu_uploader->device()));
+  writer.WriteImage(gpu_image, region, Semaphore::New(gpu_uploader->device()));
   writer.Submit();
 }
 
@@ -161,8 +163,11 @@
   FXL_DCHECK(image_factory);
   FXL_DCHECK(gpu_uploader);
 
-  return NewImageFromPixels(image_factory, gpu_uploader, pixels,
-                            vk::Format::eR8G8B8A8Unorm, width, height);
+  auto gpu_image = NewGpuImageFromPixels(
+      image_factory, vk::Format::eR8G8B8A8Unorm, width, height);
+
+  WritePixelsToImage(gpu_uploader, pixels, gpu_image);
+  return gpu_image;
 }
 
 ImagePtr NewCheckerboardImage(ImageFactory* image_factory,
@@ -172,9 +177,12 @@
   FXL_DCHECK(image_factory);
   FXL_DCHECK(gpu_uploader);
 
+  auto gpu_image = NewGpuImageFromPixels(
+      image_factory, vk::Format::eR8G8B8A8Unorm, width, height);
+
   auto pixels = NewCheckerboardPixels(width, height);
-  return NewImageFromPixels(image_factory, gpu_uploader, pixels.get(),
-                            vk::Format::eR8G8B8A8Unorm, width, height);
+  WritePixelsToImage(gpu_uploader, pixels.get(), gpu_image);
+  return gpu_image;
 }
 
 ImagePtr NewGradientImage(ImageFactory* image_factory,
@@ -185,8 +193,11 @@
   FXL_DCHECK(gpu_uploader);
 
   auto pixels = NewGradientPixels(width, height);
-  return NewImageFromPixels(image_factory, gpu_uploader, pixels.get(),
-                            vk::Format::eR8G8B8A8Unorm, width, height);
+  auto gpu_image = NewGpuImageFromPixels(
+      image_factory, vk::Format::eR8G8B8A8Unorm, width, height);
+
+  WritePixelsToImage(gpu_uploader, pixels.get(), gpu_image);
+  return gpu_image;
 }
 
 ImagePtr NewNoiseImage(ImageFactory* image_factory,
@@ -198,9 +209,11 @@
   FXL_DCHECK(gpu_uploader);
 
   auto pixels = NewNoisePixels(width, height);
-  return NewImageFromPixels(image_factory, gpu_uploader, pixels.get(),
-                            vk::Format::eR8Unorm, width, height,
-                            additional_flags);
+  auto gpu_image = NewGpuImageFromPixels(image_factory, vk::Format::eR8Unorm,
+                                         width, height, additional_flags);
+
+  WritePixelsToImage(gpu_uploader, pixels.get(), gpu_image);
+  return gpu_image;
 }
 
 std::unique_ptr<uint8_t[]> NewCheckerboardPixels(uint32_t width,
diff --git a/public/lib/escher/util/image_utils.h b/public/lib/escher/util/image_utils.h
index 1807a4c..e4875a0 100644
--- a/public/lib/escher/util/image_utils.h
+++ b/public/lib/escher/util/image_utils.h
@@ -6,6 +6,7 @@
 
 #include "lib/escher/escher.h"
 #include "lib/escher/forward_declarations.h"
+#include "lib/escher/util/image_formats.h"
 #include "lib/escher/vk/image.h"
 
 namespace escher {
@@ -47,21 +48,24 @@
 // |image_factory| is a generic interface that could be an Image cache (in which
 // case a new Image might be created, or an existing one reused). Alternatively
 // the factory could allocate a new Image every time.
-ImagePtr NewImageFromPixels(
+ImagePtr NewGpuImageFromPixels(
     ImageFactory* image_factory,
-    impl::GpuUploader* gpu_uploader,
-    uint8_t* pixels,
     vk::Format format,
     uint32_t width,
     uint32_t height,
     vk::ImageUsageFlags additional_flags = vk::ImageUsageFlags());
 
-// Write the contents of |pixels| into an existing |image|.
-// The VkFormat, width, and height of |pixels| is assumed to match that of
-// |image|.
-void WritePixelsToImage(impl::GpuUploader* gpu_uploader,
-                        uint8_t* pixels,
-                        ImagePtr image);
+// Write the contents of |pixels| into an existing |gpu_image|.
+// The width, and height of |pixels| is assumed to match that of
+// |gpu_image|.
+// If the format of |pixels| is different from |gpu_image|, a conversion
+// function that can convert from |pixels| to |gpu_image| should be
+// provided as |convertion_func|.
+void WritePixelsToImage(
+    impl::GpuUploader* gpu_uploader,
+    uint8_t* pixels,
+    ImagePtr gpu_image,
+    const escher::image_formats::ImgConvertFunc& convertion_func = nullptr);
 
 // Return new Image containing the provided pixels.  Uses transfer queue to
 // efficiently transfer image data to GPU.  If bytes is null, don't bother
diff --git a/public/lib/images/fidl/image_info.fidl b/public/lib/images/fidl/image_info.fidl
index 48bac43..b6b530f 100644
--- a/public/lib/images/fidl/image_info.fidl
+++ b/public/lib/images/fidl/image_info.fidl
@@ -9,6 +9,12 @@
     // Equivalent to Skia |kBGRA_8888_SkColorType| color type.
     // Equivalent to Zircon |ARGB_8888| pixel format on little-endian arch.
     BGRA_8 = 0,
+    // A 32-bit component that contains information for 2 pixels:
+    // Byte order: Y1, U, Y2, V
+    // Unpacks to 2 RGB pixels, where RGB1 = func(Y1, U, V)
+    // and RGB2 = func(Y2, U, V)
+    // Equivalent to YUV422
+    YUY2 = 1,
   };
 
   // Specifies how pixel color information should be interpreted.
@@ -40,6 +46,23 @@
     NON_PREMULTIPLIED = 2,
   };
 
+  enum Transform {
+    // Pixels are displayed normally.
+    NORMAL = 0,
+
+    // Pixels are mirrored left-right.
+    FLIP_HORIZONTAL = 1,
+
+    // Pixels are flipped vertically.
+    FLIP_VERTICAL = 2,
+
+    // Pixels are flipped vertically and mirrored left-right.
+    FLIP_VERTICAL_AND_HORIZONTAL = 3,
+  };
+
+  // Specifies if the image should be mirrored before displaying.
+  Transform transform = Transform.NORMAL;
+
   // The width and height of the image in pixels.
   uint32 width;
   uint32 height;
diff --git a/public/lib/ui/scenic/client/resources.cc b/public/lib/ui/scenic/client/resources.cc
index 5a9d777..4d9ec47 100644
--- a/public/lib/ui/scenic/client/resources.cc
+++ b/public/lib/ui/scenic/client/resources.cc
@@ -110,6 +110,8 @@
   switch (image_info.pixel_format) {
     case scenic::ImageInfo::PixelFormat::BGRA_8:
       return image_info.height * image_info.stride;
+    case scenic::ImageInfo::PixelFormat::YUY2:
+      return image_info.height * image_info.stride;
   }
 
   FXL_NOTREACHED();