/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#elif _DARWIN_C_SOURCE
#else
#include <linux/videodev2.h>
#endif
#include "android/camera/camera-format-converters.h"

#define  E(...)    derror(__VA_ARGS__)
#define  W(...)    dwarning(__VA_ARGS__)
#define  D(...)    VERBOSE_PRINT(camera,__VA_ARGS__)
#define  D_ACTIVE  VERBOSE_CHECK(camera)

 /*
 * NOTE: RGB and big/little endian considerations. Wherewer in this code RGB
 * pixels are represented as WORD, or DWORD, the color order inside the
 * WORD / DWORD matches the one that would occur if that WORD / DWORD would have
 * been read from the typecasted framebuffer:
 *
 *      const uint32_t rgb = *reinterpret_cast<const uint32_t*>(framebuffer);
 *
 * So, if this code runs on the little endian CPU, red color in 'rgb' would be
 * masked as 0x000000ff, and blue color would be masked as 0x00ff0000, while if
 * the code runs on a big endian CPU, the red color in 'rgb' would be masked as
 * 0xff000000, and blue color would be masked as 0x0000ff00,
 */

/*
 * RGB565 color masks
 */

#ifndef HOST_WORDS_BIGENDIAN
static const uint16_t kRed5     = 0x001f;
static const uint16_t kGreen6   = 0x07e0;
static const uint16_t kBlue5    = 0xf800;
#else   // !HOST_WORDS_BIGENDIAN
static const uint16_t kRed5     = 0xf800;
static const uint16_t kGreen6   = 0x07e0;
static const uint16_t kBlue5    = 0x001f;
#endif  // !HOST_WORDS_BIGENDIAN

/*
 * RGB32 color masks
 */

#ifndef HOST_WORDS_BIGENDIAN
static const uint32_t kRed8     = 0x000000ff;
static const uint32_t kGreen8   = 0x0000ff00;
static const uint32_t kBlue8    = 0x00ff0000;
#else   // !HOST_WORDS_BIGENDIAN
static const uint32_t kRed8     = 0x00ff0000;
static const uint32_t kGreen8   = 0x0000ff00;
static const uint32_t kBlue8    = 0x000000ff;
#endif  // !HOST_WORDS_BIGENDIAN

/*
 * Extracting, and saving color bytes from / to WORD / DWORD RGB.
 */

#ifndef HOST_WORDS_BIGENDIAN
/* Extract red, green, and blue bytes from RGB565 word. */
#define R16(rgb)    (uint8_t)((rgb) & kRed5)
#define G16(rgb)    (uint8_t)(((rgb) & kGreen6) >> 5)
#define B16(rgb)    (uint8_t)(((rgb) & kBlue5) >> 11)
/* Make 8 bits red, green, and blue, extracted from RGB565 word. */
#define R16_32(rgb) (uint8_t)((((rgb) & kRed5) << 3) | (((rgb) & kRed5) >> 2))
#define G16_32(rgb) (uint8_t)((((rgb) & kGreen6) >> 3) | (((rgb) & kGreen6) >> 9))
#define B16_32(rgb) (uint8_t)((((rgb) & kBlue5) >> 8) | (((rgb) & kBlue5) >> 14))
/* Extract red, green, and blue bytes from RGB32 dword. */
#define R32(rgb)    (uint8_t)((rgb) & kRed8)
#define G32(rgb)    (uint8_t)((((rgb) & kGreen8) >> 8) & 0xff)
#define B32(rgb)    (uint8_t)((((rgb) & kBlue8) >> 16) & 0xff)
/* Build RGB565 word from red, green, and blue bytes. */
#define RGB565(r, g, b) (uint16_t)(((((uint16_t)(b) << 6) | (g)) << 5) | (r))
/* Build RGB32 dword from red, green, and blue bytes. */
#define RGB32(r, g, b) (uint32_t)(((((uint32_t)(b) << 8) | (g)) << 8) | (r))
#else   // !HOST_WORDS_BIGENDIAN
/* Extract red, green, and blue bytes from RGB565 word. */
#define R16(rgb)    (uint8_t)(((rgb) & kRed5) >> 11)
#define G16(rgb)    (uint8_t)(((rgb) & kGreen6) >> 5)
#define B16(rgb)    (uint8_t)((rgb) & kBlue5)
/* Make 8 bits red, green, and blue, extracted from RGB565 word. */
#define R16_32(rgb) (uint8_t)((((rgb) & kRed5) >> 8) | (((rgb) & kRed5) >> 14))
#define G16_32(rgb) (uint8_t)((((rgb) & kGreen6) >> 3) | (((rgb) & kGreen6) >> 9))
#define B16_32(rgb) (uint8_t)((((rgb) & kBlue5) << 3) | (((rgb) & kBlue5) >> 2))
/* Extract red, green, and blue bytes from RGB32 dword. */
#define R32(rgb)    (uint8_t)(((rgb) & kRed8) >> 16)
#define G32(rgb)    (uint8_t)(((rgb) & kGreen8) >> 8)
#define B32(rgb)    (uint8_t)((rgb) & kBlue8)
/* Build RGB565 word from red, green, and blue bytes. */
#define RGB565(r, g, b) (uint16_t)(((((uint16_t)(r) << 6) | (g)) << 5) | (b))
/* Build RGB32 dword from red, green, and blue bytes. */
#define RGB32(r, g, b) (uint32_t)(((((uint32_t)(r) << 8) | (g)) << 8) | (b))
#endif  // !HOST_WORDS_BIGENDIAN

/*
 * BAYER bitmasks
 */

/* Bitmask for 8-bits BAYER pixel. */
#define kBayer8     0xff
/* Bitmask for 10-bits BAYER pixel. */
#define kBayer10    0x3ff
/* Bitmask for 12-bits BAYER pixel. */
#define kBayer12    0xfff

/* An union that simplifies breaking 32 bit RGB into separate R, G, and B colors.
 */
typedef union RGB32_t {
    uint32_t    color;
    struct {
#ifndef HOST_WORDS_BIGENDIAN
        uint8_t r; uint8_t g; uint8_t b; uint8_t a;
#else   // !HOST_WORDS_BIGENDIAN
        uint8_t a; uint8_t b; uint8_t g; uint8_t r;
#endif  // HOST_WORDS_BIGENDIAN
    };
} RGB32_t;

/* Clips a value to the unsigned 0-255 range, treating negative values as zero.
 */
static __inline__ int
clamp(int x)
{
    if (x > 255) return 255;
    if (x < 0)   return 0;
    return x;
}

/********************************************************************************
 * Basics of RGB -> YUV conversion
 *******************************************************************************/

/*
 * RGB -> YUV conversion macros
 */
#define RGB2Y(r, g, b) (uint8_t)(((66 * (r) + 129 * (g) +  25 * (b) + 128) >> 8) +  16)
#define RGB2U(r, g, b) (uint8_t)(((-38 * (r) - 74 * (g) + 112 * (b) + 128) >> 8) + 128)
#define RGB2V(r, g, b) (uint8_t)(((112 * (r) - 94 * (g) -  18 * (b) + 128) >> 8) + 128)

/* Converts R8 G8 B8 color to YUV. */
static __inline__ void
R8G8B8ToYUV(uint8_t r, uint8_t g, uint8_t b, uint8_t* y, uint8_t* u, uint8_t* v)
{
    *y = RGB2Y((int)r, (int)g, (int)b);
    *u = RGB2U((int)r, (int)g, (int)b);
    *v = RGB2V((int)r, (int)g, (int)b);
}

/* Converts RGB565 color to YUV. */
static __inline__ void
RGB565ToYUV(uint16_t rgb, uint8_t* y, uint8_t* u, uint8_t* v)
{
    R8G8B8ToYUV(R16_32(rgb), G16_32(rgb), B16_32(rgb), y, u, v);
}

/* Converts RGB32 color to YUV. */
static __inline__ void
RGB32ToYUV(uint32_t rgb, uint8_t* y, uint8_t* u, uint8_t* v)
{
    RGB32_t rgb_c;
    rgb_c.color = rgb;
    R8G8B8ToYUV(rgb_c.r, rgb_c.g, rgb_c.b, y, u, v);
}

/********************************************************************************
 * Basics of YUV -> RGB conversion.
 *******************************************************************************/

/*
 * YUV -> RGB conversion macros
 */

/* "Optimized" macros that take specialy prepared Y, U, and V values:
 *  C = Y - 16
 *  D = U - 128
 *  E = V - 128
 */
#define YUV2RO(C, D, E) clamp((298 * (C) + 409 * (E) + 128) >> 8)
#define YUV2GO(C, D, E) clamp((298 * (C) - 100 * (D) - 208 * (E) + 128) >> 8)
#define YUV2BO(C, D, E) clamp((298 * (C) + 516 * (D) + 128) >> 8)

/*
 *  Main macros that take the original Y, U, and V values
 */
#define YUV2R(y, u, v) clamp((298 * ((y)-16) + 409 * ((v)-128) + 128) >> 8)
#define YUV2G(y, u, v) clamp((298 * ((y)-16) - 100 * ((u)-128) - 208 * ((v)-128) + 128) >> 8)
#define YUV2B(y, u, v) clamp((298 * ((y)-16) + 516 * ((u)-128) + 128) >> 8)


/* Converts YUV color to RGB565. */
static __inline__ uint16_t
YUVToRGB565(int y, int u, int v)
{
    /* Calculate C, D, and E values for the optimized macro. */
    y -= 16; u -= 128; v -= 128;
    const uint16_t r = YUV2RO(y,u,v) >> 3;
    const uint16_t g = YUV2GO(y,u,v) >> 2;
    const uint16_t b = YUV2BO(y,u,v) >> 3;
    return RGB565(r, g, b);
}

/* Converts YUV color to RGB32. */
static __inline__ uint32_t
YUVToRGB32(int y, int u, int v)
{
    /* Calculate C, D, and E values for the optimized macro. */
    y -= 16; u -= 128; v -= 128;
    RGB32_t rgb;
    rgb.r = YUV2RO(y,u,v);
    rgb.g = YUV2GO(y,u,v);
    rgb.b = YUV2BO(y,u,v);
    return rgb.color;
}

/* Converts YUV color to separated RGB32 colors. */
static __inline__ void
YUVToRGBPix(int y, int u, int v, uint8_t* r, uint8_t* g, uint8_t* b)
{
    /* Calculate C, D, and E values for the optimized macro. */
    y -= 16; u -= 128; v -= 128;
    *r = (uint8_t)YUV2RO(y,u,v);
    *g = (uint8_t)YUV2GO(y,u,v);
    *b = (uint8_t)YUV2BO(y,u,v);
}

/* Computes a luminance value after taking the exposure compensation.
 * value into account.
 *
 * Param:
 * inputY - The input luminance value.
 * Return:
 * The luminance value after adjusting the exposure compensation.
 */
static __inline__ uint8_t
_change_exposure(uint8_t inputY, float exp_comp)
{
    return (uint8_t)clamp((float)inputY * exp_comp);
}

/* Adjusts an RGB pixel for the given exposure compensation. */
static __inline__ void
_change_exposure_RGB(uint8_t* r, uint8_t* g, uint8_t* b, float exp_comp)
{
    uint8_t y, u, v;
    R8G8B8ToYUV(*r, *g, *b, &y, &u, &v);
    YUVToRGBPix(_change_exposure(y, exp_comp), u, v, r, g, b);
}

/* Adjusts an RGB pixel for the given exposure compensation. */
static __inline__ void
_change_exposure_RGB_i(int* r, int* g, int* b, float exp_comp)
{
    uint8_t y, u, v;
    R8G8B8ToYUV(*r, *g, *b, &y, &u, &v);
    y = _change_exposure(y, exp_comp);
    *r = YUV2RO(y,u,v);
    *g = YUV2GO(y,u,v);
    *b = YUV2BO(y,u,v);
}

/* Computes the pixel value after adjusting the white balance to the current
 * one. The input the y, u, v channel of the pixel and the adjusted value will
 * be stored in place. The adjustment is done in RGB space.
 */
static __inline__ void
_change_white_balance_YUV(uint8_t* y,
                          uint8_t* u,
                          uint8_t* v,
                          float r_scale,
                          float g_scale,
                          float b_scale)
{
    int r = (float)(YUV2R((int)*y, (int)*u, (int)*v)) / r_scale;
    int g = (float)(YUV2G((int)*y, (int)*u, (int)*v)) / g_scale;
    int b = (float)(YUV2B((int)*y, (int)*u, (int)*v)) / b_scale;

    *y = RGB2Y(r, g, b);
    *u = RGB2U(r, g, b);
    *v = RGB2V(r, g, b);
}

/* Computes the pixel value after adjusting the white balance to the current
 * one. The input the r, and b channels of the pixel and the adjusted value will
 * be stored in place.
 */
static __inline__ void
_change_white_balance_RGB(int* r,
                          int* g,
                          int* b,
                          float r_scale,
                          float g_scale,
                          float b_scale)
{
    *r = (float)*r / r_scale;
    *g = (float)*g / g_scale;
    *b = (float)*b / b_scale;
}

/* Computes the pixel value after adjusting the white balance to the current
 * one. The input the r, and b channels of the pixel and the adjusted value will
 * be stored in place.
 */
static __inline__ void
_change_white_balance_RGB_b(uint8_t* r,
                            uint8_t* g,
                            uint8_t* b,
                            float r_scale,
                            float g_scale,
                            float b_scale)
{
    *r = (float)*r / r_scale;
    *g = (float)*g / g_scale;
    *b = (float)*b / b_scale;
}

/********************************************************************************
 * Generic converters between YUV and RGB formats
 *******************************************************************************/

/*
 * The converters go line by line, convering one frame format to another.
 * It's pretty much straight forward for RGB/BRG, where all colors are
 * grouped next to each other in memory. The only two things that differ one RGB
 * format from another are:
 * - Is it an RGB, or BRG (i.e. color ordering)
 * - Is it 16, 24, or 32 bits format.
 * All these differences are addressed by load_rgb / save_rgb routines, provided
 * for each format in the RGB descriptor to load / save RGB color bytes from / to
 * the buffer. As far as moving from one RGB pixel to the next, there
 * are two question to consider:
 * - How many bytes it takes to encode one RGB pixel (could be 2, 3, or 4)
 * - How many bytes it takes to encode a line (i.e. line alignment issue, which
 *   makes sence only for 24-bit formats, since 16, and 32 bit formats provide
 *   automatic word alignment.)
 * The first question is answered with the 'rgb_inc' field of the RGB descriptor,
 * and the second one is done by aligning rgb pointer up to the nearest 16 bit
 * boundaries at the end of each line.
 * YUV format has much greater complexity for conversion. in YUV color encoding
 * is divided into three separate panes that can be mixed together in any way
 * imaginable. Fortunately, there are still some consistent patterns in different

 * YUV formats that can be abstracted through a descriptor:
 * - At the line-by-line level, colors are always groupped aroud pairs of pixels,
 *   where each pixel in the pair has its own Y value, and each pair of pixels
 *   share thesame U, and V values.
 * - Position of Y, U, and V is the same for each pair, so the distance between
 *   Ys, and U/V for adjacent pairs is the same.
 * - Inside the pair, the distance between two Ys is always the same.

 * Moving between the lines in YUV can also be easily formalized. Essentially,
 * there are three ways how color panes are arranged:
 * 1. All interleaved, where all three Y, U, and V values are encoded together in
 *    one block:
 *               1,2  3,4  5,6      n,n+1
 *              YUVY YUVY YUVY .... YUVY
 *
 *    This type is used to encode YUV 4:2:2 formats.
 *
 * 2. One separate block of memory for Y pane, and one separate block of memory
 *    containing interleaved U, and V panes.
 *
 *      YY | YY | YY | YY
 *      YY | YY | YY | YY
 *      -----------------
 *      UV | UV | UV | UV
 *      -----------------
 *
 *    This type is used to encode 4:2:0 formats.
 *
 * 3. Three separate blocks of memory for each pane.
 *
 *      YY | YY | YY | YY
 *      YY | YY | YY | YY
 *      -----------------
 *      U  | U  | U  | U
 *      V  | V  | V  | V
 *      -----------------
 *
 *    This type is also used to encode 4:2:0 formats.
 *
 * Note that in cases 2, and 3 each pair of U and V is shared among four pixels,
 * grouped together as they are groupped in the framebeffer: divide the frame's
 * rectangle into 2x2 pixels squares, starting from 0,0 corner - and each square
 * represents the group of pixels that share same pair of UV values. So, each
 * line in the U/V panes table is shared between two adjacent lines in Y pane,
 * which provides a pattern on how to move between U/V lines as we move between
 * Y lines.
 *
 * So, all these patterns can be coded in a YUV format descriptor, so there can
 * just one generic way of walking YUV frame.
 *
 * BAYER format.
 * We don't use BAYER inside the guest system, so there is no need to have a
 * converter to the BAYER formats, only from it. The color approximation  used in
 * the BAYER format converters implemented here simply averages corresponded
 * color values found in pixels sorrounding the one for which RGB colors are
 * calculated.
 *
 * Performance considerations:
 * Since converters implemented here are intended to work as part of the camera
 * emulation, making the code super performant is not a priority at all. There
 * will be enough loses in other parts of the emultion to overlook any slight
 * inefficiences in the conversion algorithm as neglectable.
 */

typedef struct RGBDesc RGBDesc;
typedef struct YUVDesc YUVDesc;
typedef struct BayerDesc BayerDesc;

/* Prototype for a routine that loads RGB colors from an RGB/BRG stream.
 * Param:
 *  rgb - Pointer to a pixel inside the stream where to load colors from.
 *  r, g, b - Upon return will contain red, green, and blue colors for the pixel
 *      addressed by 'rgb' pointer.
 * Return:
 *  Pointer to the next pixel in the stream.
 */
typedef const void* (*load_rgb_func)(const void* rgb,
                                     uint8_t* r,
                                     uint8_t* g,
                                     uint8_t* b);

/* Prototype for a routine that saves RGB colors to an RGB/BRG stream.
 * Param:
 *  rgb - Pointer to a pixel inside the stream where to save colors.
 *  r, g, b - Red, green, and blue colors to save to the pixel addressed by
 *      'rgb' pointer.
 * Return:
 *  Pointer to the next pixel in the stream.
 */
typedef void* (*save_rgb_func)(void* rgb, uint8_t r, uint8_t g, uint8_t b);

/* Prototype for a routine that calculates an offset of the first U value for the
 * given line in a YUV framebuffer.
 * Param:
 *  desc - Descriptor for the YUV frame for which the offset is being calculated.
 *  line - Zero-based line number for which to calculate the offset.
 *  width, height - Frame dimensions.
 * Return:
 *  Offset of the first U value for the given frame line. The offset returned
 *  here is relative to the beginning of the YUV framebuffer.
 */
typedef int (*u_offset_func)(const YUVDesc* desc, int line, int width, int height);

/* Prototype for a routine that calculates an offset of the first V value for the
 * given line in a YUV framebuffer.
 * Param:
 *  desc - Descriptor for the YUV frame for which the offset is being calculated.
 *  line - Zero-based line number for which to calculate the offset.
 *  width, height - Frame dimensions.
 * Return:
 *  Offset of the first V value for the given frame line. The offset returned
 *  here is relative to the beginning of the YUV framebuffer.
 */
typedef int (*v_offset_func)(const YUVDesc* desc, int line, int width, int height);

/* RGB/BRG format descriptor. */
struct RGBDesc {
    /* Routine that loads RGB colors from a buffer. */
    load_rgb_func   load_rgb;
    /* Routine that saves RGB colors into a buffer. */
    save_rgb_func   save_rgb;
    /* Byte size of an encoded RGB pixel. */
    int             rgb_inc;
};

/* YUV format descriptor. */
struct YUVDesc {
    /* Offset of the first Y value in a fully interleaved YUV framebuffer. */
    int             Y_offset;
    /* Distance between two Y values inside a pair of pixels in a fully
     * interleaved YUV framebuffer. */
    int             Y_inc;
    /* Distance between first Y values of the adjacent pixel pairs in a fully
     * interleaved YUV framebuffer. */
    int             Y_next_pair;
    /* Increment between adjacent U/V values in a YUV framebuffer. */
    int             UV_inc;
    /* Controls location of the first U value in YUV framebuffer. Depending on
     * the actual YUV format can mean three things:
     *  - For fully interleaved YUV formats contains offset of the first U value
     *    in each line.
     *  - For YUV format that use separate, but interleaved UV pane, this field
     *    contains an offset of the first U value in the UV pane.
     *  - For YUV format that use fully separated Y, U, and V panes this field
     *    defines order of U and V panes in the framebuffer:
     *      = 1 - U pane comes first, right after Y pane.
     *      = 0 - U pane follows V pane that startes right after Y pane. */
    int             U_offset;
    /* Controls location of the first V value in YUV framebuffer.
     * See comments to U_offset for more info. */
    int             V_offset;
    /* Routine that calculates an offset of the first U value for the given line
     * in a YUV framebuffer. */
    u_offset_func   u_offset;
    /* Routine that calculates an offset of the first V value for the given line
     * in a YUV framebuffer. */
    v_offset_func   v_offset;
};

/* Bayer format descriptor. */
struct BayerDesc {
    /* Defines color ordering in the BAYER framebuffer. Can be one of the four:
     *  - "GBRG" for GBGBGB / RGRGRG
     *  - "GRBG" for GRGRGR / BGBGBG
     *  - "RGGB" for RGRGRG / GBGBGB
     *  - "BGGR" for BGBGBG / GRGRGR
     */
    const char* color_order;
    /* Bitmask for valid bits in the pixel:
     *  - 0xff  For a 8-bit BAYER format
     *  - 0x3ff For a 10-bit BAYER format
     *  - 0xfff For a 12-bit BAYER format
     */
    int         mask;
};

/********************************************************************************
 * RGB/BRG load / save routines.
 *******************************************************************************/

/* Loads R, G, and B colors from a RGB32 framebuffer. */
static const void*
_load_RGB32(const void* rgb, uint8_t* r, uint8_t* g, uint8_t* b)
{
    const uint8_t* rgb_ptr = (const uint8_t*)rgb;
    *r = rgb_ptr[0]; *g = rgb_ptr[1]; *b = rgb_ptr[2];
    return rgb_ptr + 4;
}

/* Saves R, G, and B colors to a RGB32 framebuffer. */
static void*
_save_RGB32(void* rgb, uint8_t r, uint8_t g, uint8_t b)
{
    uint8_t* rgb_ptr = (uint8_t*)rgb;
    rgb_ptr[0] = r; rgb_ptr[1] = g; rgb_ptr[2] = b;
    return rgb_ptr + 4;
}

/* Loads R, G, and B colors from a BRG32 framebuffer. */
static const void*
_load_BRG32(const void* rgb, uint8_t* r, uint8_t* g, uint8_t* b)
{
    const uint8_t* rgb_ptr = (const uint8_t*)rgb;
    *r = rgb_ptr[2]; *g = rgb_ptr[1]; *b = rgb_ptr[0];
    return rgb_ptr + 4;
}

/* Saves R, G, and B colors to a BRG32 framebuffer. */
static void*
_save_BRG32(void* rgb, uint8_t r, uint8_t g, uint8_t b)
{
    uint8_t* rgb_ptr = (uint8_t*)rgb;
    rgb_ptr[2] = r; rgb_ptr[1] = g; rgb_ptr[0] = b;
    return rgb_ptr + 4;
}

/* Loads R, G, and B colors from a RGB24 framebuffer.
 * Note that it's the caller's responsibility to ensure proper alignment of the
 * returned pointer at the line's break. */
static const void*
_load_RGB24(const void* rgb, uint8_t* r, uint8_t* g, uint8_t* b)
{
    const uint8_t* rgb_ptr = (const uint8_t*)rgb;
    *r = rgb_ptr[0]; *g = rgb_ptr[1]; *b = rgb_ptr[2];
    return rgb_ptr + 3;
}

/* Saves R, G, and B colors to a RGB24 framebuffer.
 * Note that it's the caller's responsibility to ensure proper alignment of the
 * returned pointer at the line's break. */
static void*
_save_RGB24(void* rgb, uint8_t r, uint8_t g, uint8_t b)
{
    uint8_t* rgb_ptr = (uint8_t*)rgb;
    rgb_ptr[0] = r; rgb_ptr[1] = g; rgb_ptr[2] = b;
    return rgb_ptr + 3;
}

/* Loads R, G, and B colors from a BRG32 framebuffer.
 * Note that it's the caller's responsibility to ensure proper alignment of the
 * returned pointer at the line's break. */
static const void*
_load_BRG24(const void* rgb, uint8_t* r, uint8_t* g, uint8_t* b)
{
    const uint8_t* rgb_ptr = (const uint8_t*)rgb;
    *r = rgb_ptr[2]; *g = rgb_ptr[1]; *b = rgb_ptr[0];
    return rgb_ptr + 3;
}

/* Saves R, G, and B colors to a BRG24 framebuffer.
 * Note that it's the caller's responsibility to ensure proper alignment of the
 * returned pointer at the line's break. */
static void*
_save_BRG24(void* rgb, uint8_t r, uint8_t g, uint8_t b)
{
    uint8_t* rgb_ptr = (uint8_t*)rgb;
    rgb_ptr[2] = r; rgb_ptr[1] = g; rgb_ptr[0] = b;
    return rgb_ptr + 3;
}

/* Loads R, G, and B colors from a RGB565 framebuffer. */
static const void*
_load_RGB16(const void* rgb, uint8_t* r, uint8_t* g, uint8_t* b)
{
    const uint16_t rgb16 = *(const uint16_t*)rgb;
    *r = R16(rgb16); *g = G16(rgb16); *b = B16(rgb16);
    return (const uint8_t*)rgb + 2;
}

/* Saves R, G, and B colors to a RGB565 framebuffer. */
static void*
_save_RGB16(void* rgb, uint8_t r, uint8_t g, uint8_t b)
{
    *(uint16_t*)rgb = RGB565(r & 0x1f, g & 0x3f, b & 0x1f);
    return (uint8_t*)rgb + 2;
}

/* Loads R, G, and B colors from a BRG565 framebuffer. */
static const void*
_load_BRG16(const void* rgb, uint8_t* r, uint8_t* g, uint8_t* b)
{
    const uint16_t rgb16 = *(const uint16_t*)rgb;
    *r = B16(rgb16); *g = G16(rgb16); *b = R16(rgb16);
    return (const uint8_t*)rgb + 2;
}

/* Saves R, G, and B colors to a BRG565 framebuffer. */
static void*
_save_BRG16(void* rgb, uint8_t r, uint8_t g, uint8_t b)
{
    *(uint16_t*)rgb = RGB565(b & 0x1f, g & 0x3f, r & 0x1f);
    return (uint8_t*)rgb + 2;
}

/********************************************************************************
 * YUV's U/V offset calculation routines.
 *******************************************************************************/

/* U offset in a fully interleaved YUV 4:2:2 */
static int
_UOffIntrlYUV(const YUVDesc* desc, int line, int width, int height)
{
    /* In interleaved YUV 4:2:2 each pair of pixels is encoded with 4 consecutive
     * bytes (or 2 bytes per pixel). So line size in a fully interleaved YUV 4:2:2
     * is twice its width. */
    return line * width * 2 + desc->U_offset;
}

/* V offset in a fully interleaved YUV 4:2:2 */
static int
_VOffIntrlYUV(const YUVDesc* desc, int line, int width, int height)
{
    /* See _UOffIntrlYUV comments. */
    return line * width * 2 + desc->V_offset;
}

/* U offset in an interleaved UV pane of YUV 4:2:0 */
static int
_UOffIntrlUV(const YUVDesc* desc, int line, int width, int height)
{
    /* UV pane starts right after the Y pane, that occupies 'height * width'
     * bytes. Eacht line in UV pane contains width / 2 'UV' pairs, which makes UV
     * lane to contain as many bytes, as the width is.
     * Each line in the UV pane is shared between two Y lines. So, final formula
     * for the beggining of the UV pane's line for the given line in YUV
     * framebuffer is:
     *
     *    height * width + (line / 2) * width = (height + line / 2) * width
     */
    return (height + line / 2) * width + desc->U_offset;
}

/* V offset in an interleaved UV pane of YUV 4:2:0 */
static int
_VOffIntrlUV(const YUVDesc* desc, int line, int width, int height)
{
    /* See comments in _UOffIntrlUV. */
    return (height + line / 2) * width + desc->V_offset;
}

/* U offset in a 3-pane YUV 4:2:0 */
static int
_UOffSepYUV(const YUVDesc* desc, int line, int width, int height)
{
    /* U, or V pane starts right after the Y pane, that occupies 'height * width'
     * bytes. Eacht line in each of U and V panes contains width / 2 elements.
     * Also, each line in each of U and V panes is shared between two Y lines.
     * So, final formula for the beggining of a line in the U/V pane is:
     *
     *    <Y pane size> + (line / 2) * width / 2
     *
     * for the pane that follows right after the Y pane, or
     *
     *    <Y pane size> + <Y pane size> / 4 + (line / 2) * width / 2
     *
     * for the second pane.
     */
    const int y_pane_size = height * width;
    if (desc->U_offset) {
        /* U pane comes right after the Y pane. */
        return y_pane_size + (line / 2) * width / 2;
    } else {
        /* U pane follows V pane. */
        return y_pane_size + y_pane_size / 4 + (line / 2) * width / 2;
    }
}

/* V offset in a 3-pane YUV 4:2:0 */
static int
_VOffSepYUV(const YUVDesc* desc, int line, int width, int height)
{
    /* See comment for _UOffSepYUV. */
    const int y_pane_size = height * width;
    if (desc->V_offset) {
        /* V pane comes right after the Y pane. */
        return y_pane_size + (line / 2) * width / 2;
    } else {
        /* V pane follows U pane. */
        return y_pane_size + y_pane_size / 4 + (line / 2) * width / 2;
    }
}

/********************************************************************************
 * Bayer routines.
 *******************************************************************************/

/* Gets a color value for the given pixel in a bayer framebuffer.
 * Param:
 *  desc - Bayer framebuffer descriptor.
 *  buf - Beginning of the framebuffer.
 *  x, y - Coordinates of the pixel inside the framebuffer to get the color for.
 *  width - Number of pixel in a line inside the framebuffer.
 * Return:
 *  Given pixel color.
 */
static __inline__ int
_get_bayer_color(const BayerDesc* desc, const void* buf, int x, int y, int width)
{
    if (desc->mask == kBayer8) {
        /* Each pixel is represented with one byte. */
        return *((const uint8_t*)buf + y * width + x);
    } else {
#ifndef HOST_WORDS_BIGENDIAN
        return *((const int16_t*)buf + y * width + x) & desc->mask;
#else
        const uint8_t* pixel = (const uint8_t*)buf + (y * width + x) * 2;
        return (((uint16_t)pixel[1] << 8) | pixel[0]) & desc->mask;
#endif  /* !HOST_WORDS_BIGENDIAN */
    }
}

/* Gets an average value of colors that are horisontally adjacent to a pixel in
 * a bayer framebuffer.
 * Param:
 *  desc - Bayer framebuffer descriptor.
 *  buf - Beginning of the framebuffer.
 *  x, y - Coordinates of the pixel inside the framebuffer that is the center for
 *      the calculation.
 *  width, height - Framebuffer dimensions.
 * Return:
 *  Average color for horisontally adjacent pixels.
 */
static int
_get_bayer_ave_hor(const BayerDesc* desc,
                   const void* buf,
                   int x,
                   int y,
                   int width,
                   int height)
{
    if (x == 0) {
        return _get_bayer_color(desc, buf, x + 1, y, width);
    } else if (x == (width - 1)) {
        return _get_bayer_color(desc, buf, x - 1, y, width);
    } else {
        return (_get_bayer_color(desc, buf, x - 1, y, width) +
                _get_bayer_color(desc, buf, x + 1, y, width)) / 2;
    }
}

/* Gets an average value of colors that are vertically adjacent to a pixel in
 * a bayer framebuffer.
 * Param:
 *  desc - Bayer framebuffer descriptor.
 *  buf - Beginning of the framebuffer.
 *  x, y - Coordinates of the pixel inside the framebuffer that is the center for
 *      the calculation.
 *  width, height - Framebuffer dimensions.
 * Return:
 *  Average color for vertically adjacent pixels.
 */
static int
_get_bayer_ave_vert(const BayerDesc* desc,
                    const void* buf,
                    int x,
                    int y,
                    int width,
                    int height)
{
    if (y == 0) {
        return _get_bayer_color(desc, buf, x, y + 1, width);
    } else if (y == (height - 1)) {
        return _get_bayer_color(desc, buf, x, y - 1, width);
    } else {
        return (_get_bayer_color(desc, buf, x, y - 1, width) +
                _get_bayer_color(desc, buf, x, y + 1, width)) / 2;
    }
}

/* Gets an average value of colors that are horisontally and vertically adjacent
 * to a pixel in a bayer framebuffer.
 * Param:
 *  desc - Bayer framebuffer descriptor.
 *  buf - Beginning of the framebuffer.
 *  x, y - Coordinates of the pixel inside the framebuffer that is the center for
 *      the calculation.
 *  width, height - Framebuffer dimensions.
 * Return:
 *  Average color for horisontally and vertically adjacent pixels.
 */
static int
_get_bayer_ave_cross(const BayerDesc* desc,
                     const void* buf,
                     int x,
                     int y,
                     int width,
                     int height)
{
    if (x > 0 && x < (width - 1) && y > 0 && y < (height - 1)) {
        /* Most of the time the code will take this path. So it makes sence to
         * special case it for performance reasons. */
        return (_get_bayer_color(desc, buf, x - 1, y, width) +
                _get_bayer_color(desc, buf, x + 1, y, width) +
                _get_bayer_color(desc, buf, x, y - 1, width) +
                _get_bayer_color(desc, buf, x, y + 1, width)) / 4;
    } else {
        int sum = 0;
        int num = 0;

        /* Horisontal sum */
        if (x == 0) {
            sum += _get_bayer_color(desc, buf, x + 1, y, width);
            num++;
        } else if (x == (width - 1)) {
            sum += _get_bayer_color(desc, buf, x - 1, y, width);
            num++;
        } else {
            sum += _get_bayer_color(desc, buf, x - 1, y, width) +
                   _get_bayer_color(desc, buf, x + 1, y, width);
            num += 2;
        }

        /* Vertical sum */
        if (y == 0) {
            sum += _get_bayer_color(desc, buf, x, y + 1, width);
            num++;
        } else if (y == (height - 1)) {
            sum += _get_bayer_color(desc, buf, x, y - 1, width);
            num++;
        } else {
            sum += _get_bayer_color(desc, buf, x, y - 1, width) +
                   _get_bayer_color(desc, buf, x, y + 1, width);
            num += 2;
        }

        return sum / num;
    }
}

/* Gets an average value of colors that are diagonally adjacent to a pixel in a
 * bayer framebuffer.
 * Param:
 *  desc - Bayer framebuffer descriptor.
 *  buf - Beginning of the framebuffer.
 *  x, y - Coordinates of the pixel inside the framebuffer that is the center for
 *      the calculation.
 *  width, height - Framebuffer dimensions.
 * Return:
 *  Average color for diagonally adjacent pixels.
 */
static int
_get_bayer_ave_diag(const BayerDesc* desc,
                    const void* buf,
                    int x,
                    int y,
                    int width,
                    int height)
{
    if (x > 0 && x < (width - 1) && y > 0 && y < (height - 1)) {
        /* Most of the time the code will take this path. So it makes sence to
         * special case it for performance reasons. */
        return (_get_bayer_color(desc, buf, x - 1, y - 1, width) +
                _get_bayer_color(desc, buf, x + 1, y - 1, width) +
                _get_bayer_color(desc, buf, x - 1, y + 1, width) +
                _get_bayer_color(desc, buf, x + 1, y + 1, width)) / 4;
    } else {
        int sum = 0;
        int num = 0;
        int xx, yy;
        for (xx = x - 1; xx < (x + 2); xx += 2) {
            for (yy = y - 1; yy < (y + 2); yy += 2) {
                if (xx >= 0 && yy >= 0 && xx < width && yy < height) {
                    sum += _get_bayer_color(desc, buf, xx, yy, width);
                    num++;
                }
            }
        }
        return sum / num;
    }
}

/* Gets pixel color selector for the given pixel in a bayer framebuffer.
 * Param:
 *  desc - Bayer framebuffer descriptor.
 *  x, y - Coordinates of the pixel inside the framebuffer to get the color
 *      selector for.
 * Return:
 *  Pixel color selector:
 *      - 'R' - pixel is red.
 *      - 'G' - pixel is green.
 *      - 'B' - pixel is blue.
 */
static __inline__ char
_get_bayer_color_sel(const BayerDesc* desc, int x, int y)
{
    return desc->color_order[((y & 1) << 1) | (x & 1)];
}

/* Calculates RGB colors for a pixel in a bayer framebuffer.
 * Param:
 *  desc - Bayer framebuffer descriptor.
 *  buf - Beginning of the framebuffer.
 *  x, y - Coordinates of the pixel inside the framebuffer to get the colors for.
 *  width, height - Framebuffer dimensions.
 *  red, green bluu - Upon return will contain RGB colors calculated for the pixel.
 */
static void
_get_bayerRGB(const BayerDesc* desc,
              const void* buf,
              int x,
              int y,
              int width,
              int height,
              int* red,
              int* green,
              int* blue)
{
    const char pixel_color = _get_bayer_color_sel(desc, x, y);

    if (pixel_color == 'G') {
        /* This is a green pixel. */
        const char next_pixel_color = _get_bayer_color_sel(desc, x + 1, y);
        *green = _get_bayer_color(desc, buf, x, y, width);
        if (next_pixel_color == 'R') {
            *red = _get_bayer_ave_hor(desc, buf, x, y, width, height);
            *blue = _get_bayer_ave_vert(desc, buf, x, y, width, height);
        } else {
            *red = _get_bayer_ave_vert(desc, buf, x, y, width, height);
            *blue = _get_bayer_ave_hor(desc, buf, x, y, width, height);
        }
    } else if (pixel_color == 'R') {
        /* This is a red pixel. */
        *red = _get_bayer_color(desc, buf, x, y, width);
        *green = _get_bayer_ave_cross(desc, buf, x, y, width, height);
        *blue = _get_bayer_ave_diag(desc, buf, x, y, width, height);
    } else {
        /* This is a blue pixel. */
        *blue = _get_bayer_color(desc, buf, x, y, width);
        *green = _get_bayer_ave_cross(desc, buf, x, y, width, height);
        *red = _get_bayer_ave_diag(desc, buf, x, y, width, height);
    }
}

/********************************************************************************
 * Generic YUV/RGB/BAYER converters
 *******************************************************************************/

/* Generic converter from an RGB/BRG format to a YUV format. */
static void
RGBToYUV(const RGBDesc* rgb_fmt,
         const YUVDesc* yuv_fmt,
         const void* rgb,
         void* yuv,
         int width,
         int height,
         float r_scale,
         float g_scale,
         float b_scale,
         float exp_comp)
{
    int y, x;
    const int Y_Inc = yuv_fmt->Y_inc;
    const int UV_inc = yuv_fmt->UV_inc;
    const int Y_next_pair = yuv_fmt->Y_next_pair;
    uint8_t* pY = (uint8_t*)yuv + yuv_fmt->Y_offset;
    for (y = 0; y < height; y++) {
        uint8_t* pU =
            (uint8_t*)yuv + yuv_fmt->u_offset(yuv_fmt, y, width, height);
        uint8_t* pV =
            (uint8_t*)yuv + yuv_fmt->v_offset(yuv_fmt, y, width, height);
        for (x = 0; x < width; x += 2,
                               pY += Y_next_pair, pU += UV_inc, pV += UV_inc) {
            uint8_t r, g, b;
            rgb = rgb_fmt->load_rgb(rgb, &r, &g, &b);
            _change_white_balance_RGB_b(&r, &g, &b, r_scale, g_scale, b_scale);
            _change_exposure_RGB(&r, &g, &b, exp_comp);
            R8G8B8ToYUV(r, g, b, pY, pU, pV);
            rgb = rgb_fmt->load_rgb(rgb, &r, &g, &b);
            _change_white_balance_RGB_b(&r, &g, &b, r_scale, g_scale, b_scale);
            _change_exposure_RGB(&r, &g, &b, exp_comp);
            pY[Y_Inc] = RGB2Y((int)r, (int)g, (int)b);
        }
        /* Aling rgb_ptr to 16 bit */
        if (((uintptr_t)rgb & 1) != 0) rgb = (const uint8_t*)rgb + 1;
    }
}

/* Generic converter from one RGB/BRG format to another RGB/BRG format. */
static void
RGBToRGB(const RGBDesc* src_rgb_fmt,
         const RGBDesc* dst_rgb_fmt,
         const void* src_rgb,
         void* dst_rgb,
         int width,
         int height,
         float r_scale,
         float g_scale,
         float b_scale,
         float exp_comp)
{
    int x, y;
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            uint8_t r, g, b;
            src_rgb = src_rgb_fmt->load_rgb(src_rgb, &r, &g, &b);
            _change_white_balance_RGB_b(&r, &g, &b, r_scale, g_scale, b_scale);
            _change_exposure_RGB(&r, &g, &b, exp_comp);
            dst_rgb = dst_rgb_fmt->save_rgb(dst_rgb, r, g, b);
        }
        /* Aling rgb pinters to 16 bit */
        if (((uintptr_t)src_rgb & 1) != 0) src_rgb = (uint8_t*)src_rgb + 1;
        if (((uintptr_t)dst_rgb & 1) != 0) dst_rgb = (uint8_t*)dst_rgb + 1;
    }
}

/* Generic converter from a YUV format to an RGB/BRG format. */
static void
YUVToRGB(const YUVDesc* yuv_fmt,
         const RGBDesc* rgb_fmt,
         const void* yuv,
         void* rgb,
         int width,
         int height,
         float r_scale,
         float g_scale,
         float b_scale,
         float exp_comp)
{
    int y, x;
    const int Y_Inc = yuv_fmt->Y_inc;
    const int UV_inc = yuv_fmt->UV_inc;
    const int Y_next_pair = yuv_fmt->Y_next_pair;
    const uint8_t* pY = (const uint8_t*)yuv + yuv_fmt->Y_offset;
    for (y = 0; y < height; y++) {
        const uint8_t* pU =
            (const uint8_t*)yuv + yuv_fmt->u_offset(yuv_fmt, y, width, height);
        const uint8_t* pV =
            (const uint8_t*)yuv + yuv_fmt->v_offset(yuv_fmt, y, width, height);
        for (x = 0; x < width; x += 2,
                               pY += Y_next_pair, pU += UV_inc, pV += UV_inc) {
            uint8_t r, g, b;
            const uint8_t U = *pU;
            const uint8_t V = *pV;
            YUVToRGBPix(*pY, U, V, &r, &g, &b);
            _change_white_balance_RGB_b(&r, &g, &b, r_scale, g_scale, b_scale);
            _change_exposure_RGB(&r, &g, &b, exp_comp);
            rgb = rgb_fmt->save_rgb(rgb, r, g, b);
            YUVToRGBPix(pY[Y_Inc], U, V, &r, &g, &b);
            _change_white_balance_RGB_b(&r, &g, &b, r_scale, g_scale, b_scale);
            _change_exposure_RGB(&r, &g, &b, exp_comp);
            rgb = rgb_fmt->save_rgb(rgb, r, g, b);
        }
        /* Aling rgb_ptr to 16 bit */
        if (((uintptr_t)rgb & 1) != 0) rgb = (uint8_t*)rgb + 1;
    }
}

/* Generic converter from one YUV format to another YUV format. */
static void
YUVToYUV(const YUVDesc* src_fmt,
         const YUVDesc* dst_fmt,
         const void* src,
         void* dst,
         int width,
         int height,
         float r_scale,
         float g_scale,
         float b_scale,
         float exp_comp)
{
    int y, x;
    const int Y_Inc_src = src_fmt->Y_inc;
    const int UV_inc_src = src_fmt->UV_inc;
    const int Y_next_pair_src = src_fmt->Y_next_pair;
    const int Y_Inc_dst = dst_fmt->Y_inc;
    const int UV_inc_dst = dst_fmt->UV_inc;
    const int Y_next_pair_dst = dst_fmt->Y_next_pair;
    const uint8_t* pYsrc = (const uint8_t*)src + src_fmt->Y_offset;
    uint8_t* pYdst = (uint8_t*)dst + dst_fmt->Y_offset;
    for (y = 0; y < height; y++) {
        const uint8_t* pUsrc =
            (const uint8_t*)src + src_fmt->u_offset(src_fmt, y, width, height);
        const uint8_t* pVsrc =
            (const uint8_t*)src + src_fmt->v_offset(src_fmt, y, width, height);
        uint8_t* pUdst =
            (uint8_t*)dst + dst_fmt->u_offset(dst_fmt, y, width, height);
        uint8_t* pVdst =
            (uint8_t*)dst + dst_fmt->v_offset(dst_fmt, y, width, height);
        for (x = 0; x < width; x += 2, pYsrc += Y_next_pair_src,
                                       pUsrc += UV_inc_src,
                                       pVsrc += UV_inc_src,
                                       pYdst += Y_next_pair_dst,
                                       pUdst += UV_inc_dst,
                                       pVdst += UV_inc_dst) {
            *pYdst = *pYsrc; *pUdst = *pUsrc; *pVdst = *pVsrc;
            _change_white_balance_YUV(pYdst, pUdst, pVdst, r_scale, g_scale, b_scale);
            *pYdst = _change_exposure(*pYdst, exp_comp);
            pYdst[Y_Inc_dst] = _change_exposure(pYsrc[Y_Inc_src], exp_comp);
        }
    }
}

/* Generic converter from a BAYER format to an RGB/BRG format. */
static void
BAYERToRGB(const BayerDesc* bayer_fmt,
           const RGBDesc* rgb_fmt,
           const void* bayer,
           void* rgb,
           int width,
           int height,
           float r_scale,
           float g_scale,
           float b_scale,
           float exp_comp)
{
    int y, x;
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            int r, g, b;
            _get_bayerRGB(bayer_fmt, bayer, x, y, width, height, &r, &g, &b);
            if (bayer_fmt->mask == kBayer10) {
                r >>= 2; g >>= 2; b >>= 2;
            } else if (bayer_fmt->mask == kBayer12) {
                r >>= 4; g >>= 4; b >>= 4;
            }
            _change_white_balance_RGB(&r, &g, &b, r_scale, g_scale, b_scale);
            _change_exposure_RGB_i(&r, &g, &b, exp_comp);
            rgb = rgb_fmt->save_rgb(rgb, r, g, b);
        }
        /* Aling rgb_ptr to 16 bit */
        if (((uintptr_t)rgb & 1) != 0) rgb = (uint8_t*)rgb + 1;
    }
}

/* Generic converter from a BAYER format to a YUV format. */
static void
BAYERToYUV(const BayerDesc* bayer_fmt,
           const YUVDesc* yuv_fmt,
           const void* bayer,
           void* yuv,
           int width,
           int height,
           float r_scale,
           float g_scale,
           float b_scale,
           float exp_comp)
{
    int y, x;
    const int Y_Inc = yuv_fmt->Y_inc;
    const int UV_inc = yuv_fmt->UV_inc;
    const int Y_next_pair = yuv_fmt->Y_next_pair;
    uint8_t* pY = (uint8_t*)yuv + yuv_fmt->Y_offset;
    for (y = 0; y < height; y++) {
        uint8_t* pU =
            (uint8_t*)yuv + yuv_fmt->u_offset(yuv_fmt, y, width, height);
        uint8_t* pV =
            (uint8_t*)yuv + yuv_fmt->v_offset(yuv_fmt, y, width, height);
        for (x = 0; x < width; x += 2,
                               pY += Y_next_pair, pU += UV_inc, pV += UV_inc) {
            int r, g, b;
            _get_bayerRGB(bayer_fmt, bayer, x, y, width, height, &r, &g, &b);
            _change_white_balance_RGB(&r, &g, &b, r_scale, g_scale, b_scale);
            _change_exposure_RGB_i(&r, &g, &b, exp_comp);
            R8G8B8ToYUV(r, g, b, pY, pU, pV);
            _get_bayerRGB(bayer_fmt, bayer, x + 1, y, width, height, &r, &g, &b);
            _change_white_balance_RGB(&r, &g, &b, r_scale, g_scale, b_scale);
            _change_exposure_RGB_i(&r, &g, &b, exp_comp);
            pY[Y_Inc] = RGB2Y(r, g, b);
        }
    }
}

/********************************************************************************
 * RGB format descriptors.
 */

/* Describes RGB32 format. */
static const RGBDesc _RGB32 =
{
    .load_rgb   = _load_RGB32,
    .save_rgb   = _save_RGB32,
    .rgb_inc    = 4
};

/* Describes BRG32 format. */
static const RGBDesc _BRG32 =
{
    .load_rgb   = _load_BRG32,
    .save_rgb   = _save_BRG32,
    .rgb_inc    = 4
};

/* Describes RGB24 format. */
static const RGBDesc _RGB24 =
{
    .load_rgb   = _load_RGB24,
    .save_rgb   = _save_RGB24,
    .rgb_inc    = 3
};

/* Describes BRG24 format. */
static const RGBDesc _BRG24 =
{
    .load_rgb   = _load_BRG24,
    .save_rgb   = _save_BRG24,
    .rgb_inc    = 3
};

/* Describes RGB16 format. */
static const RGBDesc _RGB16 =
{
    .load_rgb   = _load_RGB16,
    .save_rgb   = _save_RGB16,
    .rgb_inc    = 2
};

/* Describes BRG16 format. */
static const RGBDesc _BRG16 =
{
    .load_rgb   = _load_BRG16,
    .save_rgb   = _save_BRG16,
    .rgb_inc    = 2
};

/********************************************************************************
 * YUV 4:2:2 format descriptors.
 */

/* YUYV: 4:2:2, YUV are interleaved. */
static const YUVDesc _YUYV =
{
    .Y_offset       = 0,
    .Y_inc          = 2,
    .Y_next_pair    = 4,
    .UV_inc         = 4,
    .U_offset       = 1,
    .V_offset       = 3,
    .u_offset       = &_UOffIntrlYUV,
    .v_offset       = &_VOffIntrlYUV
};

/* UYVY: 4:2:2, YUV are interleaved. */
static const YUVDesc _UYVY =
{
    .Y_offset       = 1,
    .Y_inc          = 2,
    .Y_next_pair    = 4,
    .UV_inc         = 4,
    .U_offset       = 0,
    .V_offset       = 2,
    .u_offset       = &_UOffIntrlYUV,
    .v_offset       = &_VOffIntrlYUV
};

/* YVYU: 4:2:2, YUV are interleaved. */
static const YUVDesc _YVYU =
{
    .Y_offset       = 0,
    .Y_inc          = 2,
    .Y_next_pair    = 4,
    .UV_inc         = 4,
    .U_offset       = 3,
    .V_offset       = 1,
    .u_offset       = &_UOffIntrlYUV,
    .v_offset       = &_VOffIntrlYUV
};

/* VYUY: 4:2:2, YUV are interleaved. */
static const YUVDesc _VYUY =
{
    .Y_offset       = 1,
    .Y_inc          = 2,
    .Y_next_pair    = 4,
    .UV_inc         = 4,
    .U_offset       = 2,
    .V_offset       = 0,
    .u_offset       = &_UOffIntrlYUV,
    .v_offset       = &_VOffIntrlYUV
};

/* YYUV (also YUY2, YUNV, V422) : 4:2:2, YUV are interleaved. */
static const YUVDesc _YYUV =
{
    .Y_offset       = 0,
    .Y_inc          = 1,
    .Y_next_pair    = 4,
    .UV_inc         = 4,
    .U_offset       = 2,
    .V_offset       = 3,
    .u_offset       = &_UOffIntrlYUV,
    .v_offset       = &_VOffIntrlYUV
};

/* YYVU: 4:2:2, YUV are interleaved. */
static const YUVDesc _YYVU =
{
    .Y_offset       = 0,
    .Y_inc          = 1,
    .Y_next_pair    = 4,
    .UV_inc         = 4,
    .U_offset       = 3,
    .V_offset       = 2,
    .u_offset       = &_UOffIntrlYUV,
    .v_offset       = &_VOffIntrlYUV
};

/********************************************************************************
 * YUV 4:2:0 descriptors.
 */

/* YV12: 4:2:0, YUV are fully separated, U pane follows V pane */
static const YUVDesc _YV12 =
{
    .Y_offset       = 0,
    .Y_inc          = 1,
    .Y_next_pair    = 2,
    .UV_inc         = 1,
    .U_offset       = 0,
    .V_offset       = 1,
    .u_offset       = &_UOffSepYUV,
    .v_offset       = &_VOffSepYUV
};

/* YU12: 4:2:0, YUV are fully separated, V pane follows U pane */
static const YUVDesc _YU12 =
{
    .Y_offset       = 0,
    .Y_inc          = 1,
    .Y_next_pair    = 2,
    .UV_inc         = 1,
    .U_offset       = 1,
    .V_offset       = 0,
    .u_offset       = &_UOffSepYUV,
    .v_offset       = &_VOffSepYUV
};

/* NV12: 4:2:0, UV are interleaved, V follows U in UV pane */
static const YUVDesc _NV12 =
{
    .Y_offset       = 0,
    .Y_inc          = 1,
    .Y_next_pair    = 2,
    .UV_inc         = 2,
    .U_offset       = 0,
    .V_offset       = 1,
    .u_offset       = &_UOffIntrlUV,
    .v_offset       = &_VOffIntrlUV
};

/* NV21: 4:2:0, UV are interleaved, U follows V in UV pane */
static const YUVDesc _NV21 =
{
    .Y_offset       = 0,
    .Y_inc          = 1,
    .Y_next_pair    = 2,
    .UV_inc         = 2,
    .U_offset       = 1,
    .V_offset       = 0,
    .u_offset       = &_UOffIntrlUV,
    .v_offset       = &_VOffIntrlUV
};

/********************************************************************************
 * RGB bayer format descriptors.
 */

/* Descriptor for a 8-bit GBGB / RGRG format. */
static const BayerDesc _GB8 =
{
    .mask           = kBayer8,
    .color_order    = "GBRG"
};

/* Descriptor for a 8-bit GRGR / BGBG format. */
static const BayerDesc _GR8 =
{
    .mask           = kBayer8,
    .color_order    = "GRBG"
};

/* Descriptor for a 8-bit BGBG / GRGR format. */
static const BayerDesc _BG8 =
{
    .mask           = kBayer8,
    .color_order    = "BGGR"
};

/* Descriptor for a 8-bit RGRG / GBGB format. */
static const BayerDesc _RG8 =
{
    .mask           = kBayer8,
    .color_order    = "RGGB"
};

/* Descriptor for a 10-bit GBGB / RGRG format. */
static const BayerDesc _GB10 =
{
    .mask           = kBayer10,
    .color_order    = "GBRG"
};

/* Descriptor for a 10-bit GRGR / BGBG format. */
static const BayerDesc _GR10 =
{
    .mask           = kBayer10,
    .color_order    = "GRBG"
};

/* Descriptor for a 10-bit BGBG / GRGR format. */
static const BayerDesc _BG10 =
{
    .mask           = kBayer10,
    .color_order    = "BGGR"
};

/* Descriptor for a 10-bit RGRG / GBGB format. */
static const BayerDesc _RG10 =
{
    .mask           = kBayer10,
    .color_order    = "RGGB"
};

/* Descriptor for a 12-bit GBGB / RGRG format. */
static const BayerDesc _GB12 =
{
    .mask           = kBayer12,
    .color_order    = "GBRG"
};

/* Descriptor for a 12-bit GRGR / BGBG format. */
static const BayerDesc _GR12 =
{
    .mask           = kBayer12,
    .color_order    = "GRBG"
};

/* Descriptor for a 12-bit BGBG / GRGR format. */
static const BayerDesc _BG12 =
{
    .mask           = kBayer12,
    .color_order    = "BGGR"
};

/* Descriptor for a 12-bit RGRG / GBGB format. */
static const BayerDesc _RG12 =
{
    .mask           = kBayer12,
    .color_order    = "RGGB"
};


/********************************************************************************
 * List of descriptors for supported formats.
 *******************************************************************************/

/* Enumerates pixel formats supported by converters. */
typedef enum PIXFormatSel {
    /* Pixel format is RGB/BGR */
    PIX_FMT_RGB,
    /* Pixel format is YUV */
    PIX_FMT_YUV,
    /* Pixel format is BAYER */
    PIX_FMT_BAYER
} PIXFormatSel;

/* Formats entry in the list of descriptors for supported formats. */
typedef struct PIXFormat {
    /* "FOURCC" (V4L2_PIX_FMT_XXX) format type. */
    uint32_t        fourcc_type;
    /* RGB/YUV/BAYER format selector */
    PIXFormatSel    format_sel;
    union {
        /* References RGB format descriptor for that format. */
        const RGBDesc*      rgb_desc;
        /* References YUV format descriptor for that format. */
        const YUVDesc*      yuv_desc;
        /* References BAYER format descriptor for that format. */
        const BayerDesc*    bayer_desc;
    } desc;
} PIXFormat;

/* Array of supported pixel format descriptors. */
static const PIXFormat _PIXFormats[] = {
    /* RGB/BRG formats. */
    { V4L2_PIX_FMT_RGB32,   PIX_FMT_RGB,    .desc.rgb_desc = &_RGB32  },
    { V4L2_PIX_FMT_BGR32,   PIX_FMT_RGB,    .desc.rgb_desc = &_BRG32  },
    { V4L2_PIX_FMT_RGB565,  PIX_FMT_RGB,    .desc.rgb_desc = &_RGB16  },
    { V4L2_PIX_FMT_RGB24,   PIX_FMT_RGB,    .desc.rgb_desc = &_RGB24  },
    { V4L2_PIX_FMT_BGR24,   PIX_FMT_RGB,    .desc.rgb_desc = &_BRG24  },

    /* YUV 4:2:0 formats. */
    { V4L2_PIX_FMT_YVU420,  PIX_FMT_YUV,    .desc.yuv_desc = &_YV12   },
    { V4L2_PIX_FMT_YUV420,  PIX_FMT_YUV,    .desc.yuv_desc = &_YU12   },
    { V4L2_PIX_FMT_NV12,    PIX_FMT_YUV,    .desc.yuv_desc = &_NV12   },
    { V4L2_PIX_FMT_NV21,    PIX_FMT_YUV,    .desc.yuv_desc = &_NV21   },

    /* YUV 4:2:2 formats. */
    { V4L2_PIX_FMT_YUYV,    PIX_FMT_YUV,    .desc.yuv_desc = &_YUYV   },
    { V4L2_PIX_FMT_YYUV,    PIX_FMT_YUV,    .desc.yuv_desc = &_YYUV   },
    { V4L2_PIX_FMT_YVYU,    PIX_FMT_YUV,    .desc.yuv_desc = &_YVYU   },
    { V4L2_PIX_FMT_UYVY,    PIX_FMT_YUV,    .desc.yuv_desc = &_UYVY   },
    { V4L2_PIX_FMT_VYUY,    PIX_FMT_YUV,    .desc.yuv_desc = &_VYUY   },
    { V4L2_PIX_FMT_YVYU,    PIX_FMT_YUV,    .desc.yuv_desc = &_YVYU   },
    { V4L2_PIX_FMT_VYUY,    PIX_FMT_YUV,    .desc.yuv_desc = &_VYUY   },
    { V4L2_PIX_FMT_YYVU,    PIX_FMT_YUV,    .desc.yuv_desc = &_YYVU   },
    { V4L2_PIX_FMT_YUY2,    PIX_FMT_YUV,    .desc.yuv_desc = &_YUYV   },
    { V4L2_PIX_FMT_YUNV,    PIX_FMT_YUV,    .desc.yuv_desc = &_YUYV   },
    { V4L2_PIX_FMT_V422,    PIX_FMT_YUV,    .desc.yuv_desc = &_YUYV   },

    /* BAYER formats. */
    { V4L2_PIX_FMT_SBGGR8,  PIX_FMT_BAYER,  .desc.bayer_desc = &_BG8  },
    { V4L2_PIX_FMT_SGBRG8,  PIX_FMT_BAYER,  .desc.bayer_desc = &_GB8  },
    { V4L2_PIX_FMT_SGRBG8,  PIX_FMT_BAYER,  .desc.bayer_desc = &_GR8  },
    { V4L2_PIX_FMT_SRGGB8,  PIX_FMT_BAYER,  .desc.bayer_desc = &_RG8  },
    { V4L2_PIX_FMT_SBGGR10, PIX_FMT_BAYER,  .desc.bayer_desc = &_BG10 },
    { V4L2_PIX_FMT_SGBRG10, PIX_FMT_BAYER,  .desc.bayer_desc = &_GB10 },
    { V4L2_PIX_FMT_SGRBG10, PIX_FMT_BAYER,  .desc.bayer_desc = &_GR10 },
    { V4L2_PIX_FMT_SRGGB10, PIX_FMT_BAYER,  .desc.bayer_desc = &_RG10 },
    { V4L2_PIX_FMT_SBGGR12, PIX_FMT_BAYER,  .desc.bayer_desc = &_BG12 },
    { V4L2_PIX_FMT_SGBRG12, PIX_FMT_BAYER,  .desc.bayer_desc = &_GB12 },
    { V4L2_PIX_FMT_SGRBG12, PIX_FMT_BAYER,  .desc.bayer_desc = &_GR12 },
    { V4L2_PIX_FMT_SRGGB12, PIX_FMT_BAYER,  .desc.bayer_desc = &_RG12 },
};
static const int _PIXFormats_num = sizeof(_PIXFormats) / sizeof(*_PIXFormats);

/* Get an entry in the array of supported pixel format descriptors.
 * Param:
 *  pixel_format - "fourcc" pixel format to lookup an entry for.
 * Return"
 *  Pointer to the found entry, or NULL if no entry exists for the given pixel
 *  format.
 */
static const PIXFormat*
_get_pixel_format_descriptor(uint32_t pixel_format)
{
    int f;
    for (f = 0; f < _PIXFormats_num; f++) {
        if (_PIXFormats[f].fourcc_type == pixel_format) {
            return &_PIXFormats[f];
        }
    }
    W("%s: Pixel format %.4s is unknown",
      __FUNCTION__, (const char*)&pixel_format);
    return NULL;
}

/********************************************************************************
 * Public API
 *******************************************************************************/

int
has_converter(uint32_t from, uint32_t to)
{
    if (from == to) {
        /* Same format: converter esists. */
        return 1;
    }
    return _get_pixel_format_descriptor(from) != NULL &&
           _get_pixel_format_descriptor(to) != NULL;
}

int
convert_frame(const void* frame,
              uint32_t pixel_format,
              size_t framebuffer_size,
              int width,
              int height,
              ClientFrameBuffer* framebuffers,
              int fbs_num,
              float r_scale,
              float g_scale,
              float b_scale,
              float exp_comp)
{
    int n;
    const PIXFormat* src_desc = _get_pixel_format_descriptor(pixel_format);
    if (src_desc == NULL) {
        E("%s: Source pixel format %.4s is unknown",
          __FUNCTION__, (const char*)&pixel_format);
        return -1;
    }

    for (n = 0; n < fbs_num; n++) {
        /* Note that we need to apply white balance, exposure compensation, etc.
         * when we transfer the captured frame to the user framebuffer. So, even
         * if source and destination formats are the same, we will have to go
         * thrugh the converters to apply these things. */
        const PIXFormat* dst_desc =
            _get_pixel_format_descriptor(framebuffers[n].pixel_format);
        if (dst_desc == NULL) {
            E("%s: Destination pixel format %.4s is unknown",
              __FUNCTION__, (const char*)&framebuffers[n].pixel_format);
            return -1;
        }
        switch (src_desc->format_sel) {
            case PIX_FMT_RGB:
                if (dst_desc->format_sel == PIX_FMT_RGB) {
                    RGBToRGB(src_desc->desc.rgb_desc, dst_desc->desc.rgb_desc,
                             frame, framebuffers[n].framebuffer, width, height,
                             r_scale, g_scale, b_scale, exp_comp);
                } else if (dst_desc->format_sel == PIX_FMT_YUV) {
                    RGBToYUV(src_desc->desc.rgb_desc, dst_desc->desc.yuv_desc,
                             frame, framebuffers[n].framebuffer, width, height,
                             r_scale, g_scale, b_scale, exp_comp);
                } else {
                    E("%s: Unexpected destination pixel format %d",
                      __FUNCTION__, dst_desc->format_sel);
                    return -1;
                }
                break;
            case PIX_FMT_YUV:
                if (dst_desc->format_sel == PIX_FMT_RGB) {
                    YUVToRGB(src_desc->desc.yuv_desc, dst_desc->desc.rgb_desc,
                             frame, framebuffers[n].framebuffer, width, height,
                             r_scale, g_scale, b_scale, exp_comp);
                } else if (dst_desc->format_sel == PIX_FMT_YUV) {
                    YUVToYUV(src_desc->desc.yuv_desc, dst_desc->desc.yuv_desc,
                             frame, framebuffers[n].framebuffer, width, height,
                             r_scale, g_scale, b_scale, exp_comp);
                } else {
                    E("%s: Unexpected destination pixel format %d",
                      __FUNCTION__, dst_desc->format_sel);
                    return -1;
                }
                break;
            case PIX_FMT_BAYER:
                if (dst_desc->format_sel == PIX_FMT_RGB) {
                    BAYERToRGB(src_desc->desc.bayer_desc, dst_desc->desc.rgb_desc,
                              frame, framebuffers[n].framebuffer, width, height,
                              r_scale, g_scale, b_scale, exp_comp);
                } else if (dst_desc->format_sel == PIX_FMT_YUV) {
                    BAYERToYUV(src_desc->desc.bayer_desc, dst_desc->desc.yuv_desc,
                               frame, framebuffers[n].framebuffer, width, height,
                               r_scale, g_scale, b_scale, exp_comp);
                } else {
                    E("%s: Unexpected destination pixel format %d",
                      __FUNCTION__, dst_desc->format_sel);
                    return -1;
                }
                break;
            default:
                E("%s: Unexpected source pixel format %d",
                  __FUNCTION__, dst_desc->format_sel);
                return -1;
        }
    }

    return 0;
}
