Bug 1857841 - pt 3. Add a new page kind named "fresh" r=glandium
[gecko.git] / gfx / ycbcr / YCbCrUtils.cpp
blobb2b5a4f2935e835d7dfb190f35268d46f4013ebc
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/EndianUtils.h"
7 #include "gfx2DGlue.h"
8 #include "mozilla/gfx/Swizzle.h"
10 #include "YCbCrUtils.h"
11 #include "yuv_convert.h"
12 #include "ycbcr_to_rgb565.h"
13 #include "libyuv.h"
15 namespace mozilla {
16 namespace gfx {
18 // clang-format off
20 static YUVType GetYUVType(const layers::PlanarYCbCrData& aData) {
21 switch (aData.mChromaSubsampling) {
22 case ChromaSubsampling::FULL:
23 return aData.mCbCrStride > 0 ? YV24 : Y8;
24 case ChromaSubsampling::HALF_WIDTH:
25 return YV16;
26 case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT:
27 return YV12;
29 MOZ_CRASH("Unknown chroma subsampling");
32 void
33 GetYCbCrToRGBDestFormatAndSize(const layers::PlanarYCbCrData& aData,
34 SurfaceFormat& aSuggestedFormat,
35 IntSize& aSuggestedSize)
37 YUVType yuvtype = GetYUVType(aData);
39 // 'prescale' is true if the scaling is to be done as part of the
40 // YCbCr to RGB conversion rather than on the RGB data when rendered.
41 bool prescale = aSuggestedSize.width > 0 && aSuggestedSize.height > 0 &&
42 aSuggestedSize != aData.mPictureRect.Size();
44 if (aSuggestedFormat == SurfaceFormat::R5G6B5_UINT16) {
45 #if defined(HAVE_YCBCR_TO_RGB565)
46 if (prescale &&
47 !IsScaleYCbCrToRGB565Fast(aData.mPictureRect.x,
48 aData.mPictureRect.y,
49 aData.mPictureRect.width,
50 aData.mPictureRect.height,
51 aSuggestedSize.width,
52 aSuggestedSize.height,
53 yuvtype,
54 FILTER_BILINEAR) &&
55 IsConvertYCbCrToRGB565Fast(aData.mPictureRect.x,
56 aData.mPictureRect.y,
57 aData.mPictureRect.width,
58 aData.mPictureRect.height,
59 yuvtype)) {
60 prescale = false;
62 #else
63 // yuv2rgb16 function not available
64 aSuggestedFormat = SurfaceFormat::B8G8R8X8;
65 #endif
67 else if (aSuggestedFormat != SurfaceFormat::B8G8R8X8) {
68 // No other formats are currently supported.
69 aSuggestedFormat = SurfaceFormat::B8G8R8X8;
71 if (aSuggestedFormat == SurfaceFormat::B8G8R8X8) {
72 /* ScaleYCbCrToRGB32 does not support a picture offset, nor 4:4:4 data.
73 See bugs 639415 and 640073. */
74 if (aData.mPictureRect.TopLeft() != IntPoint(0, 0) || yuvtype == YV24)
75 prescale = false;
77 if (!prescale) {
78 aSuggestedSize = aData.mPictureRect.Size();
82 static inline void
83 ConvertYCbCr16to8Line(uint8_t* aDst,
84 int aStride,
85 const uint16_t* aSrc,
86 int aStride16,
87 int aWidth,
88 int aHeight,
89 int aBitDepth)
91 // These values from from the comment on from libyuv's Convert16To8Row_C:
92 int scale;
93 switch (aBitDepth) {
94 case 10:
95 scale = 16384;
96 break;
97 case 12:
98 scale = 4096;
99 break;
100 case 16:
101 scale = 256;
102 break;
103 default:
104 MOZ_ASSERT_UNREACHABLE("invalid bit depth value");
105 return;
108 libyuv::Convert16To8Plane(aSrc, aStride16, aDst, aStride, scale, aWidth, aHeight);
111 void
112 ConvertYCbCrToRGBInternal(const layers::PlanarYCbCrData& aData,
113 const SurfaceFormat& aDestFormat,
114 const IntSize& aDestSize,
115 unsigned char* aDestBuffer,
116 int32_t aStride)
118 // ConvertYCbCrToRGB et al. assume the chroma planes are rounded up if the
119 // luma plane is odd sized. Monochrome images have 0-sized CbCr planes
120 YUVType yuvtype = GetYUVType(aData);
122 // Used if converting to 8 bits YUV.
123 UniquePtr<uint8_t[]> yChannel;
124 UniquePtr<uint8_t[]> cbChannel;
125 UniquePtr<uint8_t[]> crChannel;
126 layers::PlanarYCbCrData dstData;
127 const layers::PlanarYCbCrData& srcData =
128 aData.mColorDepth == ColorDepth::COLOR_8 ? aData : dstData;
130 if (aData.mColorDepth != ColorDepth::COLOR_8) {
131 // Convert to 8 bits data first.
132 dstData.mPictureRect = aData.mPictureRect;
133 // We align the destination stride to 32 bytes, so that libyuv can use
134 // SSE optimised code.
135 auto ySize = aData.YDataSize();
136 auto cbcrSize = aData.CbCrDataSize();
137 dstData.mYStride = (ySize.width + 31) & ~31;
138 dstData.mCbCrStride = (cbcrSize.width + 31) & ~31;
139 dstData.mYUVColorSpace = aData.mYUVColorSpace;
140 dstData.mColorDepth = ColorDepth::COLOR_8;
141 dstData.mColorRange = aData.mColorRange;
142 dstData.mChromaSubsampling = aData.mChromaSubsampling;
144 size_t yMemorySize = GetAlignedStride<1>(dstData.mYStride, ySize.height);
145 size_t cbcrMemorySize =
146 GetAlignedStride<1>(dstData.mCbCrStride, cbcrSize.height);
147 if (yMemorySize == 0) {
148 MOZ_DIAGNOSTIC_ASSERT(cbcrMemorySize == 0, "CbCr without Y makes no sense");
149 return;
151 yChannel = MakeUnique<uint8_t[]>(yMemorySize);
153 dstData.mYChannel = yChannel.get();
155 int bitDepth = BitDepthForColorDepth(aData.mColorDepth);
157 ConvertYCbCr16to8Line(dstData.mYChannel,
158 dstData.mYStride,
159 reinterpret_cast<uint16_t*>(aData.mYChannel),
160 aData.mYStride / 2,
161 ySize.width,
162 ySize.height,
163 bitDepth);
165 if (cbcrMemorySize) {
166 cbChannel = MakeUnique<uint8_t[]>(cbcrMemorySize);
167 crChannel = MakeUnique<uint8_t[]>(cbcrMemorySize);
169 dstData.mCbChannel = cbChannel.get();
170 dstData.mCrChannel = crChannel.get();
172 ConvertYCbCr16to8Line(dstData.mCbChannel,
173 dstData.mCbCrStride,
174 reinterpret_cast<uint16_t*>(aData.mCbChannel),
175 aData.mCbCrStride / 2,
176 cbcrSize.width,
177 cbcrSize.height,
178 bitDepth);
180 ConvertYCbCr16to8Line(dstData.mCrChannel,
181 dstData.mCbCrStride,
182 reinterpret_cast<uint16_t*>(aData.mCrChannel),
183 aData.mCbCrStride / 2,
184 cbcrSize.width,
185 cbcrSize.height,
186 bitDepth);
190 // Convert from YCbCr to RGB now, scaling the image if needed.
191 if (aDestSize != srcData.mPictureRect.Size()) {
192 #if defined(HAVE_YCBCR_TO_RGB565)
193 if (aDestFormat == SurfaceFormat::R5G6B5_UINT16) {
194 ScaleYCbCrToRGB565(srcData.mYChannel,
195 srcData.mCbChannel,
196 srcData.mCrChannel,
197 aDestBuffer,
198 srcData.mPictureRect.x,
199 srcData.mPictureRect.y,
200 srcData.mPictureRect.width,
201 srcData.mPictureRect.height,
202 aDestSize.width,
203 aDestSize.height,
204 srcData.mYStride,
205 srcData.mCbCrStride,
206 aStride,
207 yuvtype,
208 FILTER_BILINEAR);
209 } else
210 #endif
211 ScaleYCbCrToRGB32(srcData.mYChannel, //
212 srcData.mCbChannel,
213 srcData.mCrChannel,
214 aDestBuffer,
215 srcData.mPictureRect.width,
216 srcData.mPictureRect.height,
217 aDestSize.width,
218 aDestSize.height,
219 srcData.mYStride,
220 srcData.mCbCrStride,
221 aStride,
222 yuvtype,
223 srcData.mYUVColorSpace,
224 FILTER_BILINEAR);
225 } else { // no prescale
226 #if defined(HAVE_YCBCR_TO_RGB565)
227 if (aDestFormat == SurfaceFormat::R5G6B5_UINT16) {
228 ConvertYCbCrToRGB565(srcData.mYChannel,
229 srcData.mCbChannel,
230 srcData.mCrChannel,
231 aDestBuffer,
232 srcData.mPictureRect.x,
233 srcData.mPictureRect.y,
234 srcData.mPictureRect.width,
235 srcData.mPictureRect.height,
236 srcData.mYStride,
237 srcData.mCbCrStride,
238 aStride,
239 yuvtype);
240 } else // aDestFormat != SurfaceFormat::R5G6B5_UINT16
241 #endif
242 ConvertYCbCrToRGB32(srcData.mYChannel, //
243 srcData.mCbChannel,
244 srcData.mCrChannel,
245 aDestBuffer,
246 srcData.mPictureRect.x,
247 srcData.mPictureRect.y,
248 srcData.mPictureRect.width,
249 srcData.mPictureRect.height,
250 srcData.mYStride,
251 srcData.mCbCrStride,
252 aStride,
253 yuvtype,
254 srcData.mYUVColorSpace,
255 srcData.mColorRange);
259 void ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData,
260 const SurfaceFormat& aDestFormat,
261 const IntSize& aDestSize, unsigned char* aDestBuffer,
262 int32_t aStride) {
263 ConvertYCbCrToRGBInternal(aData, aDestFormat, aDestSize, aDestBuffer,
264 aStride);
265 #if MOZ_BIG_ENDIAN()
266 // libyuv makes endian-correct result, which needs to be swapped to BGRX
267 if (aDestFormat != SurfaceFormat::R5G6B5_UINT16) {
268 gfx::SwizzleData(aDestBuffer, aStride, gfx::SurfaceFormat::X8R8G8B8,
269 aDestBuffer, aStride, gfx::SurfaceFormat::B8G8R8X8,
270 aDestSize);
272 #endif
275 void FillAlphaToRGBA(const uint8_t* aAlpha, const int32_t aAlphaStride,
276 uint8_t* aBuffer, const int32_t aWidth,
277 const int32_t aHeight, const gfx::SurfaceFormat& aFormat) {
278 MOZ_ASSERT(aAlphaStride >= aWidth);
279 MOZ_ASSERT(aFormat ==
280 SurfaceFormat::B8G8R8A8); // required for SurfaceFormatBit::OS_A
282 const int bpp = BytesPerPixel(aFormat);
283 const size_t rgbaStride = aWidth * bpp;
284 const uint8_t* src = aAlpha;
285 for (int32_t h = 0; h < aHeight; ++h) {
286 size_t offset = static_cast<size_t>(SurfaceFormatBit::OS_A) / 8;
287 for (int32_t w = 0; w < aWidth; ++w) {
288 aBuffer[offset] = src[w];
289 offset += bpp;
291 src += aAlphaStride;
292 aBuffer += rgbaStride;
296 void ConvertYCbCrAToARGB(const layers::PlanarYCbCrData& aYCbCr,
297 const layers::PlanarAlphaData& aAlpha,
298 const SurfaceFormat& aDestFormat,
299 const IntSize& aDestSize, unsigned char* aDestBuffer,
300 int32_t aStride, PremultFunc premultiplyAlphaOp) {
301 // libyuv makes endian-correct result, so the format needs to be B8G8R8A8.
302 MOZ_ASSERT(aDestFormat == SurfaceFormat::B8G8R8A8);
303 MOZ_ASSERT(aAlpha.mSize == aYCbCr.YDataSize());
305 // libyuv has libyuv::I420AlphaToARGB, but lacks support for 422 and 444.
306 // Until that's added, we'll rely on our own code to handle this more
307 // generally, rather than have a special case and more redundant code.
309 UniquePtr<uint8_t[]> alphaChannel;
310 int32_t alphaStride8bpp = 0;
311 uint8_t* alphaChannel8bpp = nullptr;
313 // This function converts non-8-bpc images to 8-bpc. (Bug 1682322)
314 ConvertYCbCrToRGBInternal(aYCbCr, aDestFormat, aDestSize, aDestBuffer,
315 aStride);
317 if (aYCbCr.mColorDepth != ColorDepth::COLOR_8) {
318 // These two lines are borrowed from ConvertYCbCrToRGBInternal, since
319 // there's not a very elegant way of sharing the logic that I can see
320 alphaStride8bpp = (aAlpha.mSize.width + 31) & ~31;
321 size_t alphaSize =
322 GetAlignedStride<1>(alphaStride8bpp, aAlpha.mSize.height);
324 alphaChannel = MakeUnique<uint8_t[]>(alphaSize);
326 ConvertYCbCr16to8Line(alphaChannel.get(), alphaStride8bpp,
327 reinterpret_cast<uint16_t*>(aAlpha.mChannel),
328 aYCbCr.mYStride / 2, aAlpha.mSize.width,
329 aAlpha.mSize.height,
330 BitDepthForColorDepth(aYCbCr.mColorDepth));
332 alphaChannel8bpp = alphaChannel.get();
333 } else {
334 alphaStride8bpp = aYCbCr.mYStride;
335 alphaChannel8bpp = aAlpha.mChannel;
338 MOZ_ASSERT(alphaStride8bpp != 0);
339 MOZ_ASSERT(alphaChannel8bpp);
341 FillAlphaToRGBA(alphaChannel8bpp, alphaStride8bpp, aDestBuffer,
342 aYCbCr.mPictureRect.width, aYCbCr.mPictureRect.height, aDestFormat);
344 if (premultiplyAlphaOp) {
345 DebugOnly<int> err =
346 premultiplyAlphaOp(aDestBuffer, aStride, aDestBuffer, aStride,
347 aYCbCr.mPictureRect.width, aYCbCr.mPictureRect.height);
348 MOZ_ASSERT(!err);
351 #if MOZ_BIG_ENDIAN()
352 // libyuv makes endian-correct result, which needs to be swapped to BGRA
353 gfx::SwizzleData(aDestBuffer, aStride, gfx::SurfaceFormat::A8R8G8B8,
354 aDestBuffer, aStride, gfx::SurfaceFormat::B8G8R8A8,
355 aYCbCr.mPictureRect.Size());
356 #endif
359 void
360 ConvertI420AlphaToARGB(const uint8_t* aSrcY,
361 const uint8_t* aSrcU,
362 const uint8_t* aSrcV,
363 const uint8_t* aSrcA,
364 int aSrcStrideYA, int aSrcStrideUV,
365 uint8_t* aDstARGB, int aDstStrideARGB,
366 int aWidth, int aHeight) {
368 ConvertI420AlphaToARGB32(aSrcY,
369 aSrcU,
370 aSrcV,
371 aSrcA,
372 aDstARGB,
373 aWidth,
374 aHeight,
375 aSrcStrideYA,
376 aSrcStrideUV,
377 aDstStrideARGB);
378 #if MOZ_BIG_ENDIAN()
379 // libyuv makes endian-correct result, which needs to be swapped to BGRA
380 gfx::SwizzleData(aDstARGB, aDstStrideARGB, gfx::SurfaceFormat::A8R8G8B8,
381 aDstARGB, aDstStrideARGB, gfx::SurfaceFormat::B8G8R8A8,
382 IntSize(aWidth, aHeight));
383 #endif
386 } // namespace gfx
387 } // namespace mozilla