Bug 634557 - ScaleYCbCrToRGB565: Reference C version, r=doublec
authorTimothy B. Terriberry <tterribe@vt.edu>
Thu, 14 Apr 2011 17:32:15 -0700
changeset 68191 e957f873a56524345a2e5f7251735f949ee5b7e5
parent 68190 b140e7746652f2783306c054ee2802db52272fd8
child 68192 e05cdd49d004d542a232edd51e13e5a9e68668d8
push id19535
push userMs2ger@gmail.com
push dateSat, 16 Apr 2011 09:25:05 +0000
treeherdermozilla-central@bee1149208a9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdoublec
bugs634557
milestone6.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 634557 - ScaleYCbCrToRGB565: Reference C version, r=doublec
gfx/layers/basic/BasicImages.cpp
gfx/ycbcr/ycbcr_to_rgb565.cpp
gfx/ycbcr/ycbcr_to_rgb565.h
--- a/gfx/layers/basic/BasicImages.cpp
+++ b/gfx/layers/basic/BasicImages.cpp
@@ -158,68 +158,97 @@ BasicPlanarYCbCrImage::SetData(const Dat
     type = gfx::YV12;
   }
   else {
     NS_ERROR("YCbCr format not supported");
   }
 
   // 'prescale' is true if the scaling is to be done as part of the
   // YCbCr to RGB conversion rather than on the RGB data when rendered.
-  // We don't prescale if the image has an offset. See bug 639415.
   PRBool prescale = mScaleHint.width > 0 && mScaleHint.height > 0 &&
-                    aData.mPicX == 0 && aData.mPicY == 0;
+                    mScaleHint != aData.mPicSize;
   if (format == gfxASurface::ImageFormatRGB16_565) {
 #if defined(HAVE_YCBCR_TO_RGB565)
-    if (prescale && gfx::IsConvertYCbCrToRGB565Fast(aData.mPicX,
-                                                    aData.mPicY,
-                                                    aData.mPicSize.width,
-                                                    aData.mPicSize.height,
-                                                    type)) {
-      // yuv2rgb16 with scale function not yet available for NEON
+    if (prescale &&
+        !gfx::IsScaleYCbCrToRGB565Fast(aData.mPicX,
+                                       aData.mPicY,
+                                       aData.mPicSize.width,
+                                       aData.mPicSize.height,
+                                       mScaleHint.width,
+                                       mScaleHint.height,
+                                       type,
+                                       gfx::FILTER_BILINEAR) &&
+        gfx::IsConvertYCbCrToRGB565Fast(aData.mPicX,
+                                        aData.mPicY,
+                                        aData.mPicSize.width,
+                                        aData.mPicSize.height,
+                                        type)) {
       prescale = PR_FALSE;
-    } else
+    }
+#else
+    // yuv2rgb16 function not available
+    format = gfxASurface::ImageFormatRGB24;
 #endif
-    {
-      // yuv2rgb16 function not yet available for non-NEON, and currently
-      // using it requires a number of extra graphics operations, so it's
-      // probably better to fall back to 24-bit RGB.
-      // See https://bugzilla.mozilla.org/show_bug.cgi?id=641196
-      format = gfxASurface::ImageFormatRGB24;
-    }
+  }
+  else if (format != gfxASurface::ImageFormatRGB24) {
+    // No other formats are currently supported.
+    format = gfxASurface::ImageFormatRGB24;
   }
+  if (format == gfxASurface::ImageFormatRGB24) {
+    /* ScaleYCbCrToRGB32 does not support a picture offset, nor 4:4:4 data.
+       See bugs 639415 and 640073. */
+    if (aData.mPicX != 0 || aData.mPicY != 0 || type == gfx::YV24)
+      prescale = PR_FALSE;
+  }
+
   gfxIntSize size(prescale ? mScaleHint.width : aData.mPicSize.width,
                   prescale ? mScaleHint.height : aData.mPicSize.height);
 
   mStride = gfxASurface::FormatStrideForWidth(format, size.width);
   mBuffer = new PRUint8[size.height * mStride];
   if (!mBuffer) {
     // out of memory
     return;
   }
 
   // Convert from YCbCr to RGB now, scaling the image if needed.
   if (size != aData.mPicSize) {
-    if (format == gfxASurface::ImageFormatRGB24) {
+#if defined(HAVE_YCBCR_TO_RGB565)
+    if (format == gfxASurface::ImageFormatRGB16_565) {
+      gfx::ScaleYCbCrToRGB565(aData.mYChannel,
+                              aData.mCbChannel,
+                              aData.mCrChannel,
+                              mBuffer,
+                              aData.mPicX,
+                              aData.mPicY,
+                              aData.mPicSize.width,
+                              aData.mPicSize.height,
+                              size.width,
+                              size.height,
+                              aData.mYStride,
+                              aData.mCbCrStride,
+                              mStride,
+                              type,
+                              gfx::FILTER_BILINEAR);
+    } else
+#endif
       gfx::ScaleYCbCrToRGB32(aData.mYChannel,
                              aData.mCbChannel,
                              aData.mCrChannel,
                              mBuffer,
                              aData.mPicSize.width,
                              aData.mPicSize.height,
                              size.width,
                              size.height,
                              aData.mYStride,
                              aData.mCbCrStride,
                              mStride,
                              type,
                              gfx::ROTATE_0,
                              gfx::FILTER_BILINEAR);
-    } else {
-       NS_ERROR("Fail, ScaleYCbCrToRGB format not supported\n");
-    }
   } else { // no prescale
 #if defined(HAVE_YCBCR_TO_RGB565)
     if (format == gfxASurface::ImageFormatRGB16_565) {
       gfx::ConvertYCbCrToRGB565(aData.mYChannel,
                                 aData.mCbChannel,
                                 aData.mCrChannel,
                                 mBuffer,
                                 aData.mPicX,
--- a/gfx/ycbcr/ycbcr_to_rgb565.cpp
+++ b/gfx/ycbcr/ycbcr_to_rgb565.cpp
@@ -33,62 +33,557 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+#include <stdlib.h>
+#include <limits.h>
+#include "nsDebug.h"
 #include "ycbcr_to_rgb565.h"
 
+
+
 #ifdef HAVE_YCBCR_TO_RGB565
 
 namespace mozilla {
 
 namespace gfx {
 
+/*This contains all of the parameters that are needed to convert a row.
+  Passing them in a struct instead of as individual parameters saves the need
+   to continually push onto the stack the ones that are fixed for every row.*/
+struct yuv2rgb565_row_scale_bilinear_ctx{
+  PRUint16 *rgb_row;
+  const PRUint8 *y_row;
+  const PRUint8 *u_row;
+  const PRUint8 *v_row;
+  int y_yweight;
+  int y_pitch;
+  int width;
+  int source_x0_q16;
+  int source_dx_q16;
+  /*Not used for 4:4:4, except with chroma-nearest.*/
+  int source_uv_xoffs_q16;
+  /*Not used for 4:4:4 or chroma-nearest.*/
+  int uv_pitch;
+  /*Not used for 4:2:2, 4:4:4, or chroma-nearest.*/
+  int uv_yweight;
+};
+
+
+
+/*This contains all of the parameters that are needed to convert a row.
+  Passing them in a struct instead of as individual parameters saves the need
+   to continually push onto the stack the ones that are fixed for every row.*/
+struct yuv2rgb565_row_scale_nearest_ctx{
+  PRUint16 *rgb_row;
+  const PRUint8 *y_row;
+  const PRUint8 *u_row;
+  const PRUint8 *v_row;
+  int width;
+  int source_x0_q16;
+  int source_dx_q16;
+  /*Not used for 4:4:4.*/
+  int source_uv_xoffs_q16;
+};
+
+
+
+typedef void (*yuv2rgb565_row_scale_bilinear_func)(
+ const yuv2rgb565_row_scale_bilinear_ctx *ctx, int dither);
+
+typedef void (*yuv2rgb565_row_scale_nearest_func)(
+ const yuv2rgb565_row_scale_nearest_ctx *ctx, int dither);
+
+
+
 # if defined(MOZILLA_MAY_SUPPORT_NEON)
 
 void __attribute((noinline)) yuv42x_to_rgb565_row_neon(uint16 *dst,
                                                        const uint8 *y,
                                                        const uint8 *u,
                                                        const uint8 *v,
                                                        int n,
                                                        int oddflag);
 
 #endif
 
-/*Convert a single pixel from Y'CbCr to RGB565.*/
-static PRUint16 yu2rgb565(int y, int u, int v) {
+
+
+/*Bilinear interpolation of a single value.
+  This uses the exact same formulas as the asm, even though it adds some extra
+   shifts that do nothing but reduce accuracy.*/
+static int bislerp(const PRUint8 *row,
+                   int pitch,
+                   int source_x,
+                   int xweight,
+                   int yweight) {
+  int a;
+  int b;
+  int c;
+  int d;
+  a = row[source_x];
+  b = row[source_x+1];
+  c = row[source_x+pitch];
+  d = row[source_x+pitch+1];
+  a = ((a<<8)+(c-a)*yweight+128)>>8;
+  b = ((b<<8)+(d-b)*yweight+128)>>8;
+  return ((a<<8)+(b-a)*xweight+128)>>8;
+}
+
+/*Convert a single pixel from Y'CbCr to RGB565.
+  This uses the exact same formulas as the asm, even though we could make the
+   constants a lot more accurate with 32-bit wide registers.*/
+static PRUint16 yu2rgb565(int y, int u, int v, int dither) {
+  /*This combines the constant offset that needs to be added during the Y'CbCr
+     conversion with a rounding offset that depends on the dither parameter.*/
+  static const int DITHER_BIAS[4][3]={
+    {-14240,    8704,    -17696},
+    {-14240+128,8704+64, -17696+128},
+    {-14240+256,8704+128,-17696+256},
+    {-14240+384,8704+192,-17696+384}
+  };
   int r;
   int g;
   int b;
-  r = NS_CLAMP((74*y+102*v-14240+256)>>9, 0, 31);
-  g = NS_CLAMP((74*y-25*u-52*v+8704+128)>>8, 0, 63);
-  b = NS_CLAMP((74*y+129*u-17696+256)>>9, 0, 31);
+  r = NS_CLAMP((74*y+102*v+DITHER_BIAS[dither][0])>>9, 0, 31);
+  g = NS_CLAMP((74*y-25*u-52*v+DITHER_BIAS[dither][1])>>8, 0, 63);
+  b = NS_CLAMP((74*y+129*u+DITHER_BIAS[dither][2])>>9, 0, 31);
   return (PRUint16)(r<<11 | g<<5 | b);
 }
 
+static void ScaleYCbCr420ToRGB565_Bilinear_Row_C(
+ const yuv2rgb565_row_scale_bilinear_ctx *ctx, int dither){
+  int x;
+  int source_x_q16;
+  source_x_q16 = ctx->source_x0_q16;
+  for (x = 0; x < ctx->width; x++) {
+    int source_x;
+    int xweight;
+    int y;
+    int u;
+    int v;
+    xweight = ((source_x_q16&0xFFFF)+128)>>8;
+    source_x = source_x_q16>>16;
+    y = bislerp(ctx->y_row, ctx->y_pitch, source_x, xweight, ctx->y_yweight);
+    xweight = (((source_x_q16+ctx->source_uv_xoffs_q16)&0x1FFFF)+256)>>9;
+    source_x = (source_x_q16+ctx->source_uv_xoffs_q16)>>17;
+    source_x_q16 += ctx->source_dx_q16;
+    u = bislerp(ctx->u_row, ctx->uv_pitch, source_x, xweight, ctx->uv_yweight);
+    v = bislerp(ctx->v_row, ctx->uv_pitch, source_x, xweight, ctx->uv_yweight);
+    ctx->rgb_row[x] = yu2rgb565(y, u, v, dither);
+    dither ^= 3;
+  }
+}
+
+static void ScaleYCbCr422ToRGB565_Bilinear_Row_C(
+ const yuv2rgb565_row_scale_bilinear_ctx *ctx, int dither){
+  int x;
+  int source_x_q16;
+  source_x_q16 = ctx->source_x0_q16;
+  for (x = 0; x < ctx->width; x++) {
+    int source_x;
+    int xweight;
+    int y;
+    int u;
+    int v;
+    xweight = ((source_x_q16&0xFFFF)+128)>>8;
+    source_x = source_x_q16>>16;
+    y = bislerp(ctx->y_row, ctx->y_pitch, source_x, xweight, ctx->y_yweight);
+    xweight = (((source_x_q16+ctx->source_uv_xoffs_q16)&0x1FFFF)+256)>>9;
+    source_x = (source_x_q16+ctx->source_uv_xoffs_q16)>>17;
+    source_x_q16 += ctx->source_dx_q16;
+    u = bislerp(ctx->u_row, ctx->uv_pitch, source_x, xweight, ctx->y_yweight);
+    v = bislerp(ctx->v_row, ctx->uv_pitch, source_x, xweight, ctx->y_yweight);
+    ctx->rgb_row[x] = yu2rgb565(y, u, v, dither);
+    dither ^= 3;
+  }
+}
+
+static void ScaleYCbCr444ToRGB565_Bilinear_Row_C(
+ const yuv2rgb565_row_scale_bilinear_ctx *ctx, int dither){
+  int x;
+  int source_x_q16;
+  source_x_q16 = ctx->source_x0_q16;
+  for (x = 0; x < ctx->width; x++) {
+    int source_x;
+    int xweight;
+    int y;
+    int u;
+    int v;
+    xweight = ((source_x_q16&0xFFFF)+128)>>8;
+    source_x = source_x_q16>>16;
+    source_x_q16 += ctx->source_dx_q16;
+    y = bislerp(ctx->y_row, ctx->y_pitch, source_x, xweight, ctx->y_yweight);
+    u = bislerp(ctx->u_row, ctx->y_pitch, source_x, xweight, ctx->y_yweight);
+    v = bislerp(ctx->v_row, ctx->y_pitch, source_x, xweight, ctx->y_yweight);
+    ctx->rgb_row[x] = yu2rgb565(y, u, v, dither);
+    dither ^= 3;
+  }
+}
+
+static void ScaleYCbCr42xToRGB565_BilinearY_Row_C(
+ const yuv2rgb565_row_scale_bilinear_ctx *ctx, int dither){
+  int x;
+  int source_x_q16;
+  source_x_q16 = ctx->source_x0_q16;
+  for (x = 0; x < ctx->width; x++) {
+    int source_x;
+    int xweight;
+    int y;
+    int u;
+    int v;
+    xweight = ((source_x_q16&0xFFFF)+128)>>8;
+    source_x = source_x_q16>>16;
+    y = bislerp(ctx->y_row, ctx->y_pitch, source_x, xweight, ctx->y_yweight);
+    source_x = (source_x_q16+ctx->source_uv_xoffs_q16)>>17;
+    source_x_q16 += ctx->source_dx_q16;
+    u = ctx->u_row[source_x];
+    v = ctx->v_row[source_x];
+    ctx->rgb_row[x] = yu2rgb565(y, u, v, dither);
+    dither ^= 3;
+  }
+}
+
+static void ScaleYCbCr444ToRGB565_BilinearY_Row_C(
+ const yuv2rgb565_row_scale_bilinear_ctx *ctx, int dither){
+  int x;
+  int source_x_q16;
+  source_x_q16 = ctx->source_x0_q16;
+  for (x = 0; x < ctx->width; x++) {
+    int source_x;
+    int xweight;
+    int y;
+    int u;
+    int v;
+    xweight = ((source_x_q16&0xFFFF)+128)>>8;
+    source_x = source_x_q16>>16;
+    y = bislerp(ctx->y_row, ctx->y_pitch, source_x, xweight, ctx->y_yweight);
+    source_x = (source_x_q16+ctx->source_uv_xoffs_q16)>>16;
+    source_x_q16 += ctx->source_dx_q16;
+    u = ctx->u_row[source_x];
+    v = ctx->v_row[source_x];
+    ctx->rgb_row[x] = yu2rgb565(y, u, v, dither);
+    dither ^= 3;
+  }
+}
+
+static void ScaleYCbCr42xToRGB565_Nearest_Row_C(
+ const yuv2rgb565_row_scale_nearest_ctx *ctx, int dither){
+  int y;
+  int u;
+  int v;
+  int x;
+  int source_x_q16;
+  int source_x;
+  source_x_q16 = ctx->source_x0_q16;
+  for (x = 0; x < ctx->width; x++) {
+    source_x = source_x_q16>>16;
+    y = ctx->y_row[source_x];
+    source_x = (source_x_q16+ctx->source_uv_xoffs_q16)>>17;
+    source_x_q16 += ctx->source_dx_q16;
+    u = ctx->u_row[source_x];
+    v = ctx->v_row[source_x];
+    ctx->rgb_row[x] = yu2rgb565(y, u, v, dither);
+    dither ^= 3;
+  }
+}
+
+static void ScaleYCbCr444ToRGB565_Nearest_Row_C(
+ const yuv2rgb565_row_scale_nearest_ctx *ctx, int dither){
+  int y;
+  int u;
+  int v;
+  int x;
+  int source_x_q16;
+  int source_x;
+  source_x_q16 = ctx->source_x0_q16;
+  for (x = 0; x < ctx->width; x++) {
+    source_x = source_x_q16>>16;
+    source_x_q16 += ctx->source_dx_q16;
+    y = ctx->y_row[source_x];
+    u = ctx->u_row[source_x];
+    v = ctx->v_row[source_x];
+    ctx->rgb_row[x] = yu2rgb565(y, u, v, dither);
+    dither ^= 3;
+  }
+}
+
+NS_GFX_(void) ScaleYCbCrToRGB565(const PRUint8 *y_buf,
+                                 const PRUint8 *u_buf,
+                                 const PRUint8 *v_buf,
+                                 PRUint8 *rgb_buf,
+                                 int source_x0,
+                                 int source_y0,
+                                 int source_width,
+                                 int source_height,
+                                 int width,
+                                 int height,
+                                 int y_pitch,
+                                 int uv_pitch,
+                                 int rgb_pitch,
+                                 YUVType yuv_type,
+                                 ScaleFilter filter) {
+  int source_x0_q16;
+  int source_y0_q16;
+  int source_dx_q16;
+  int source_dy_q16;
+  int source_uv_xoffs_q16;
+  int source_uv_yoffs_q16;
+  int x_shift;
+  int y_shift;
+  int ymin;
+  int ymax;
+  int uvmin;
+  int uvmax;
+  int dither;
+  /*We don't support negative destination rectangles (just flip the source
+     instead), and for empty ones there's nothing to do.*/
+  if (width <= 0 || height <= 0)
+    return;
+  /*These bounds are required to avoid 16.16 fixed-point overflow.*/
+  NS_ASSERTION(source_x0 > (INT_MIN>>16) && source_x0 < (INT_MAX>>16),
+    "ScaleYCbCrToRGB565 source X offset out of bounds.");
+  NS_ASSERTION(source_x0+source_width > (INT_MIN>>16)
+            && source_x0+source_width < (INT_MAX>>16),
+    "ScaleYCbCrToRGB565 source width out of bounds.");
+  NS_ASSERTION(source_y0 > (INT_MIN>>16) && source_y0 < (INT_MAX>>16),
+    "ScaleYCbCrToRGB565 source Y offset out of bounds.");
+  NS_ASSERTION(source_y0+source_height > (INT_MIN>>16)
+            && source_y0+source_height < (INT_MAX>>16),
+    "ScaleYCbCrToRGB565 source height out of bounds.");
+  /*We require the same stride for Y' and Cb and Cr for 4:4:4 content.*/
+  NS_ASSERTION(yuv_type != YV24 || y_pitch == uv_pitch,
+    "ScaleYCbCrToRGB565 luma stride differs from chroma for 4:4:4 content.");
+  /*We assume we can read outside the bounds of the input, because it makes
+     the code much simpler (and in practice is true: both Theora and VP8 return
+     padded reference frames).
+    In practice, we do not even _have_ the actual bounds of the source, as
+     we are passed a crop rectangle from it, and not the dimensions of the full
+     image.
+    This assertion will not guarantee our out-of-bounds reads are safe, but it
+     should at least catch the simple case of passing in an unpadded buffer.*/
+  NS_ASSERTION(abs(y_pitch) >= abs(source_width)+16,
+    "ScaleYCbCrToRGB565 source image unpadded?");
+  /*We take an area-based approach to pixel coverage to avoid shifting by small
+     amounts (or not so small, when up-scaling or down-scaling by a large
+     factor).
+
+    An illustrative example: scaling 4:2:0 up by 2, using JPEG chroma cositing^.
+
+    + = RGB destination locations
+    * = Y' source locations
+    - = Cb, Cr source locations
+
+    +   +   +   +  +   +   +   +
+      *       *      *       *
+    +   +   +   +  +   +   +   +
+          -              -
+    +   +   +   +  +   +   +   +
+      *       *      *       *
+    +   +   +   +  +   +   +   +
+
+    +   +   +   +  +   +   +   +
+      *       *      *       *
+    +   +   +   +  +   +   +   +
+          -              -
+    +   +   +   +  +   +   +   +
+      *       *      *       *
+    +   +   +   +  +   +   +   +
+
+    So, the coordinates of the upper-left + (first destination site) should
+     be (-0.25,-0.25) in the source Y' coordinate system.
+    Similarly, the coordinates should be (-0.375,-0.375) in the source Cb, Cr
+     coordinate system.
+    Note that the origin and scale of these two coordinate systems is not the
+     same!
+
+    ^JPEG cositing is required for Theora; VP8 doesn't specify cositing rules,
+     but nearly all software converters in existence (at least those that are
+     open source, and many that are not) use JPEG cositing instead of MPEG.*/
+  source_dx_q16 = (source_width<<16) / width;
+  source_x0_q16 = (source_x0<<16)+(source_dx_q16>>1)-0x8000;
+  source_dy_q16 = (source_height<<16) / height;
+  source_y0_q16 = (source_y0<<16)+(source_dy_q16>>1)-0x8000;
+  x_shift = (yuv_type != YV24);
+  y_shift = (yuv_type == YV12);
+  /*These two variables hold the difference between the origins of the Y' and
+     the Cb, Cr coordinate systems, using the scale of the Y' coordinate
+     system.*/
+  source_uv_xoffs_q16 = -(x_shift<<15);
+  source_uv_yoffs_q16 = -(y_shift<<15);
+  /*Compute the range of source rows we'll actually use.
+    This doesn't guarantee we won't read outside this range.*/
+  ymin = source_height >= 0 ? source_y0 : source_y0+source_height-1;
+  ymax = source_height >= 0 ? source_y0+source_height-1 : source_y0;
+  uvmin = ymin>>y_shift;
+  uvmax = ((ymax+1+y_shift)>>y_shift)-1;
+  /*Pick a dithering pattern.
+    The "&3" at the end is just in case RAND_MAX is lying.*/
+  dither = (rand()/(RAND_MAX>>2))&3;
+  /*Nearest-neighbor scaling.*/
+  if (filter == FILTER_NONE) {
+    yuv2rgb565_row_scale_nearest_ctx ctx;
+    yuv2rgb565_row_scale_nearest_func scale_row;
+    int y;
+    /*Add rounding offsets once, in advance.*/
+    source_x0_q16 += 0x8000;
+    source_y0_q16 += 0x8000;
+    source_uv_xoffs_q16 += (x_shift<<15);
+    source_uv_yoffs_q16 += (y_shift<<15);
+    if (yuv_type == YV12)
+      scale_row = ScaleYCbCr42xToRGB565_Nearest_Row_C;
+    else
+      scale_row = ScaleYCbCr444ToRGB565_Nearest_Row_C;
+    ctx.width = width;
+    ctx.source_x0_q16 = source_x0_q16;
+    ctx.source_dx_q16 = source_dx_q16;
+    ctx.source_uv_xoffs_q16 = source_uv_xoffs_q16;
+    for (y=0; y<height; y++) {
+      int source_y;
+      ctx.rgb_row = (PRUint16 *)(rgb_buf + y*rgb_pitch);
+      source_y = source_y0_q16>>16;
+      source_y = NS_CLAMP(source_y, ymin, ymax);
+      ctx.y_row = y_buf + source_y*y_pitch;
+      source_y = (source_y0_q16+source_uv_yoffs_q16)>>(16+y_shift);
+      source_y = NS_CLAMP(source_y, uvmin, uvmax);
+      source_y0_q16 += source_dy_q16;
+      ctx.u_row = u_buf + source_y*uv_pitch;
+      ctx.v_row = v_buf + source_y*uv_pitch;
+      (*scale_row)(&ctx, dither);
+      dither ^= 2;
+    }
+  }
+  /*Bilinear scaling.*/
+  else {
+    yuv2rgb565_row_scale_bilinear_ctx ctx;
+    yuv2rgb565_row_scale_bilinear_func scale_row;
+    int uvxscale_min;
+    int uvxscale_max;
+    int uvyscale_min;
+    int uvyscale_max;
+    int y;
+    /*Check how close the chroma scaling is to unity.
+      If it's close enough, we can get away with nearest-neighbor chroma
+       sub-sampling, and only doing bilinear on luma.
+      If a given axis is subsampled, we use bounds on the luma step of
+       [0.67...2], which is equivalent to scaling chroma by [1...3].
+      If it's not subsampled, we use bounds of [0.5...1.33], which is
+       equivalent to scaling chroma by [0.75...2].
+      The lower bound is chosen as a trade-off between speed and how terrible
+       nearest neighbor looks when upscaling.*/
+# define CHROMA_NEAREST_SUBSAMP_STEP_MIN  0xAAAA
+# define CHROMA_NEAREST_NORMAL_STEP_MIN   0x8000
+# define CHROMA_NEAREST_SUBSAMP_STEP_MAX 0x20000
+# define CHROMA_NEAREST_NORMAL_STEP_MAX  0x15555
+    uvxscale_min = yuv_type != YV24 ?
+     CHROMA_NEAREST_SUBSAMP_STEP_MIN : CHROMA_NEAREST_NORMAL_STEP_MIN;
+    uvxscale_max = yuv_type != YV24 ?
+     CHROMA_NEAREST_SUBSAMP_STEP_MAX : CHROMA_NEAREST_NORMAL_STEP_MAX;
+    uvyscale_min = yuv_type == YV12 ?
+     CHROMA_NEAREST_SUBSAMP_STEP_MIN : CHROMA_NEAREST_NORMAL_STEP_MIN;
+    uvyscale_max = yuv_type == YV12 ?
+     CHROMA_NEAREST_SUBSAMP_STEP_MAX : CHROMA_NEAREST_NORMAL_STEP_MAX;
+    if (uvxscale_min <= abs(source_dx_q16)
+     && abs(source_dx_q16) <= uvxscale_max
+     && uvyscale_min <= abs(source_dy_q16)
+     && abs(source_dy_q16) <= uvyscale_max) {
+      /*Add the rounding offsets now.*/
+      source_uv_xoffs_q16 += 1<<(15+x_shift);
+      source_uv_yoffs_q16 += 1<<(15+y_shift);
+      if (yuv_type != YV24)
+        scale_row = ScaleYCbCr42xToRGB565_BilinearY_Row_C;
+      else
+        scale_row = ScaleYCbCr444ToRGB565_BilinearY_Row_C;
+    }
+    else {
+      if (yuv_type == YV12)
+        scale_row = ScaleYCbCr420ToRGB565_Bilinear_Row_C;
+      else if (yuv_type == YV16)
+        scale_row = ScaleYCbCr422ToRGB565_Bilinear_Row_C;
+      else
+        scale_row = ScaleYCbCr444ToRGB565_Bilinear_Row_C;
+    }
+    ctx.width = width;
+    ctx.y_pitch = y_pitch;
+    ctx.source_x0_q16 = source_x0_q16;
+    ctx.source_dx_q16 = source_dx_q16;
+    ctx.source_uv_xoffs_q16 = source_uv_xoffs_q16;
+    ctx.uv_pitch = uv_pitch;
+    for (y=0; y<height; y++) {
+      int source_y;
+      int yweight;
+      int uvweight;
+      ctx.rgb_row = (PRUint16 *)(rgb_buf + y*rgb_pitch);
+      source_y = (source_y0_q16+128)>>16;
+      yweight = ((source_y0_q16+128)>>8)&0xFF;
+      if (source_y < ymin) {
+        source_y = ymin;
+        yweight = 0;
+      }
+      if (source_y > ymax) {
+        source_y = ymax;
+        yweight = 0;
+      }
+      ctx.y_row = y_buf + source_y*y_pitch;
+      source_y = source_y0_q16+source_uv_yoffs_q16+(128<<y_shift);
+      source_y0_q16 += source_dy_q16;
+      uvweight = source_y>>(8+y_shift)&0xFF;
+      source_y >>= 16+y_shift;
+      if (source_y < uvmin) {
+        source_y = uvmin;
+        uvweight = 0;
+      }
+      if (source_y > uvmax) {
+        source_y = uvmax;
+        uvweight = 0;
+      }
+      ctx.u_row = u_buf + source_y*uv_pitch;
+      ctx.v_row = v_buf + source_y*uv_pitch;
+      ctx.y_yweight = yweight;
+      ctx.uv_yweight = uvweight;
+      (*scale_row)(&ctx, dither);
+      dither ^= 2;
+    }
+  }
+}
+
+NS_GFX_(bool) IsScaleYCbCrToRGB565Fast(int source_x0,
+                                       int source_y0,
+                                       int source_width,
+                                       int source_height,
+                                       int width,
+                                       int height,
+                                       YUVType yuv_type,
+                                       ScaleFilter filter)
+{
+  return false;
+}
+
 
 
 void yuv_to_rgb565_row_c(uint16 *dst,
                          const uint8 *y,
                          const uint8 *u,
                          const uint8 *v,
                          int x_shift,
                          int pic_x,
                          int pic_width)
 {
   int x;
   for (x = 0; x < pic_width; x++)
   {
     dst[x] = yu2rgb565(y[pic_x+x],
                        u[(pic_x+x)>>x_shift],
-                       v[(pic_x+x)>>x_shift]);
+                       v[(pic_x+x)>>x_shift],
+                       2); // Disable dithering for now.
   }
 }
 
 NS_GFX_(void) ConvertYCbCrToRGB565(const uint8* y_buf,
                                    const uint8* u_buf,
                                    const uint8* v_buf,
                                    uint8* rgb_buf,
                                    int pic_x,
--- a/gfx/ycbcr/ycbcr_to_rgb565.h
+++ b/gfx/ycbcr/ycbcr_to_rgb565.h
@@ -31,15 +31,42 @@ NS_GFX_(void) ConvertYCbCrToRGB565(const
                                    YUVType yuv_type);
 
 // Used to test if we have an accelerated version.
 NS_GFX_(bool) IsConvertYCbCrToRGB565Fast(int pic_x,
                                          int pic_y,
                                          int pic_width,
                                          int pic_height,
                                          YUVType yuv_type);
+
+// Scale a frame of YUV to 16 bit RGB565.
+NS_GFX_(void) ScaleYCbCrToRGB565(const PRUint8 *yplane,
+                                 const PRUint8 *uplane,
+                                 const PRUint8 *vplane,
+                                 PRUint8 *rgbframe,
+                                 int source_x0,
+                                 int source_y0,
+                                 int source_width,
+                                 int source_height,
+                                 int width,
+                                 int height,
+                                 int ystride,
+                                 int uvstride,
+                                 int rgbstride,
+                                 YUVType yuv_type,
+                                 ScaleFilter filter);
+
+// Used to test if we have an accelerated version.
+NS_GFX_(bool) IsScaleYCbCrToRGB565Fast(int source_x0,
+                                       int source_y0,
+                                       int source_width,
+                                       int source_height,
+                                       int width,
+                                       int height,
+                                       YUVType yuv_type,
+                                       ScaleFilter filter);
 #endif // HAVE_YCBCR_TO_RGB565
 
 }  // namespace gfx
 
 }  // namespace mozilla
 
 #endif // MEDIA_BASE_YCBCR_TO_RGB565_H_