Skip to content

GRAPHICS - Color problems. Change native code to eliminate the RGB/BGR issues #1378

Closed
nanoframework/nf-interpreter
#2822
@TerryFogg

Description

@TerryFogg

Target name(s)

No response

Firmware version

No response

Was working before? On which version?

No response

Device capabilities

No response

Description

Changes to be made to Graphics Firmware

Recent changes somewhere in the code introduced most likely during changes to add Generic SPI drivers has caused changes to the colours.

Code was incorrectly modified to fix the problem, altering code from the original NETMF port that was working properly for many years, and had been tested and working properly.

The native graphics implementation internally is a mixture of RGB and BGR.
Historically this may be the following reason

The original IBM VGA implementation made use of the INMOS 171/176 RAMDAC chip, which is used as the Color-Lookup Table (CLUT) for VGA color mode displaying up to 256 colors from an 18-bit color palette.

By storing the palette values in the CPU RAM in BGR order, the individual byte values end up in the proper sequence for the RAMDAC when they are transferred to it, byte-by-byte, using it's 8-bit data bus.

From the original comment in graphics_decl.h of .net microFramewrok

// The PAL Graphics API uses the 24bit BGR color space, the one that's used for TinyCore and
// CLR. It is the responsibility of whoever is implementing the PAL to deal with color conversion
// as neither CLR or TinyCore understands any color space other than this default one.
// For opacity, the valid value are from 0 (c_OpacityTransparent) to 256 (c_OpacityOpaque).

Changes to the C# libraries has introduced the problem that colours from colour name are now defined as RGB where previously they were BGR.

The following code fragments show what was used by NETMF Color.cs and what is currently in the Color.cs

//  From NETMF  Color.cs - Color names are in BGR format
namespace Microsoft.SPOT.Presentation.Media
{
    public abstract class Colors
    {   
        ...
        public static readonly Color Red = (Color)0x0000FF;
		public static readonly Color Blue = (Color)0xFF0000;
		public static readonly Color Green = (Color)0x008000;
        ...
	}
}
// Current (New) Color.cs in nanoFramework.Graphics - ARGB Format
namespace System.Drawing
{
	public struct Color
	{      
        ...
		public static Color Red { get => new Color(0xFFFF0000); }
		public static Color Green { get => new Color(0xFF008000); }
		public static Color Blue { get => new Color(0xFF0000FF); }
        ...
	}
}

Goal

  • To change the code to use only RGB color space.
  • To import Graphics Tests to ensure that modification in the future pass the tests.

Reference Notes

Caution In order to flush bitmaps onto your display, you need to create the source bitmap with exactly the
same dimensions as your display, even if you flush only a part of the bitmap to your display.

The following image formats are supported by nanoFramework.

  1. Bitmap
  2. JPEG
  3. GIF

Not supported

  1. Bitmap images with indexed color palettes, as used for 16 and 256 color images.
  2. PNG and TIFF

Bitmaps

All bitmaps added to the resource file are converted and deployed to flash as RGB565. This can be seen in the VSExtension repo file ProcessResourceFiles.cs.

private byte[] GetBitmapDataBmp(Bitmap bitmap, out NanoResourceFile.CLR_GFX_BitmapDescription bitmapDescription)
{


    try
    {
        byte bitsPerPixel = 16;
        Bitmap clone = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format16bppRgb565);
        using (Graphics gr = Graphics.FromImage(clone))
        {
            gr.DrawImageUnscaled(bitmap, 0, 0);
        }

        Rectangle rect = new Rectangle(0, 0, clone.Width, clone.Height);
        BitmapData bitmapData = clone.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format16bppRgb565);

        byte[] data = new byte[clone.Width * clone.Height * 2];

        System.Runtime.InteropServices.Marshal.Copy(bitmapData.Scan0, data, 0, data.Length);
        clone.UnlockBits(bitmapData);

        bitmapDescription = new NanoResourceFile.CLR_GFX_BitmapDescription(
            (ushort)bitmap.Width, (ushort)bitmap.Height, 0, bitsPerPixel, NanoResourceFile.CLR_GFX_BitmapDescription.c_TypeBitmap);

        return data;
    }
    catch
    {
        throw new NotSupportedException($"PixelFormat ({bitmap.PixelFormat.ToString()}) could not be converted to Format16bppRgb565.");
    };
}

Bitmaps read from file or from a supplied byte array may not have been converted by the VSExtension.

Support for these bitmaps limited to

Support for these bitmaps are limited to (to be verified)

  • RGB565

  • 32 bit ARGB

  • 24 bit RGB

  • 8 bit Indexed

    switch (encodingType)
    {
    case Bmp16Bit_565:
    param.srcWidthInBytes = ((width * 16 + 31) / 32) * 4; // each line is 4-byte aligned in size
    break;
    
    case Bmp32Bit_ARGB:
    param.srcWidthInBytes = ((width * 32 + 31) / 32) * 4; // each line is 4-byte aligned in size
    break;
    
    case Bmp24Bit_RGB:
    param.srcWidthInBytes = ((width * 24 + 31) / 32) * 4; // each line is 4-byte aligned in size
    break;
    
    case Bmp8Bit_Indexed:
    param.srcWidthInBytes = ((width * 8 + 31) / 32) * 4; // each line is 4-byte aligned in size
    param.palette = palette;
    param.paletteDepth = paletteDepth;
    break;
    
    case BmpUnknown:
    NANOCLR_SET_AND_LEAVE(CLR_E_NOT_SUPPORTED);
    break;
    
    default:
    NANOCLR_SET_AND_LEAVE(CLR_E_NOT_SUPPORTED);
    }

GIFs

Note

The GIF format supports up to 8 bits per pixel for each image, allowing a single image to reference its own palette of up to 256 different colors chosen from the 24-bit RGB color space. It also supports animations and allows a separate palette of up to 256 colors for each frame.

GIF is palette-based: the colors used in an image (a frame) in the file have their RGB values defined in a palette table that can hold up to 256 entries, and the data for the image refer to the colors by their indices (0–255) in the palette table. The color definitions in the palette can be drawn from a color space of millions of shades (224 shades, 8 bits for each primary), but the maximum number of colors a frame can use is 256. This limitation seemed reasonable when GIF was developed because few people could afford the hardware to display more colors simultaneously. Simple graphics, line drawings, cartoons, and grey-scale photographs typically need fewer than 256 colors.

Note: nanoFramework does not support animated GIFs or interlaced GIFs.

All GIFs added to the resource file are simply deployed to the device without conversion. This can be seen in the VSExtension repo file ProcessResourceFiles.cs.

private byte[] GetBitmapDataGif(Bitmap bitmap, out NanoResourceFile.CLR_GFX_BitmapDescription bitmapDescription)
{
    return GetBitmapDataRaw(bitmap, out bitmapDescription, NanoResourceFile.CLR_GFX_BitmapDescription.c_TypeGif);
}
private byte[] GetBitmapDataRaw(Bitmap bitmap, out NanoResourceFile.CLR_GFX_BitmapDescription bitmapDescription, byte type)
{
    bitmapDescription = new NanoResourceFile.CLR_GFX_BitmapDescription((ushort)bitmap.Width, (ushort)bitmap.Height, 0, 1, type);

    MemoryStream stream = new MemoryStream();

    // need to copy the Bitmap in order to access it, otherwise it will throw an exception
    using (Bitmap bmpCopy = new Bitmap(bitmap))
    {
        bmpCopy.Save(stream, bitmap.RawFormat);
    }

    stream.Capacity = (int)stream.Length;
    return stream.GetBuffer();
}

GIF Native Software

The nanoFramework appears to use some custom code with some common routines for LZW decompression.

JPEG

JPEF files are encoded 8 bit red, 8 bit green, 8 bit blue files.

All JPEG added to the resource file are simply deployed to the device without conversion. This can be seen in the VSExtension repo file ProcessResourceFiles.cs.

private byte[] GetBitmapDataJpeg(Bitmap bitmap, out NanoResourceFile.CLR_GFX_BitmapDescription bitmapDescription)
{
    return GetBitmapDataRaw(bitmap, out bitmapDescription, NanoResourceFile.CLR_GFX_BitmapDescription.c_TypeJpeg);
}
private byte[] GetBitmapDataRaw(Bitmap bitmap, out NanoResourceFile.CLR_GFX_BitmapDescription bitmapDescription, byte type)
{
    bitmapDescription = new NanoResourceFile.CLR_GFX_BitmapDescription((ushort)bitmap.Width, (ushort)bitmap.Height, 0, 1, type);

    MemoryStream stream = new MemoryStream();

    // need to copy the Bitmap in order to access it, otherwise it will throw an exception
    using (Bitmap bmpCopy = new Bitmap(bitmap))
    {
        bmpCopy.Save(stream, bitmap.RawFormat);
    }

    stream.Capacity = (int)stream.Length;
    return stream.GetBuffer();
}

JPEG Native Software

The nanoFramework uses **libjpeg **version 6b. The latest version is 9e. There is a change log file in version 9e detailing the fixes and added features. Each version of the libraries since inception can be found here Directory Listing of /files (ijg.org). Information relating to this library can be found here libjpeg - Wikipedia.

The libjpeg files are found in "\src\nanoFramework.Graphics\Graphics\Core\Support\Jpeg", the library currently includes both decompression and some of the compression files which may be referenced by the decompression libraries. There does seem to be a couple of compression related files that are not referenced.

Added to the library are some files to support nanoFramework, jpeg.cpp, jbytearraydatasrc.c and jmemnanoclr.cpp .

Also jdcolor.c has been modified to include some bgr support.

// Added method from original library
METHODDEF(void)
bgr_ycc_convert(j_compress_ptr cinfo,
    JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
    JDIMENSION output_row, int num_rows)
// Added method from original library
METHODDEF(void)
    bgr_gray_convert(j_compress_ptr cinfo,
                     JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
                     JDIMENSION output_row, int num_rows)
// Added support for OCS_BGR and OCS_BGRA for nanoFramework
jinit_color_converter(j_compress_ptr cinfo)
{
    ...
    ...
     /* Check num_components, set conversion method based on requested space */
    switch (cinfo->jpeg_color_space) {
    case JCS_GRAYSCALE:
       ...
       ...
        }
        else if (cinfo->in_color_space == OCS_BGR) {
            cconvert->pub.start_pass = rgb_ycc_start;
            cconvert->pub.color_convert = bgr_gray_convert;
        }
        else if (cinfo->in_color_space == OCS_BGRA) {
            cconvert->pub.start_pass = rgb_ycc_start;
            cconvert->pub.color_convert = bgr_gray_convert;
        }
    case JCS_YCbCr:
       ...
        else if (cinfo->in_color_space == OCS_BGR) {
            cconvert->pub.start_pass = rgb_ycc_start;
            cconvert->pub.color_convert = bgr_ycc_convert;
        }
        else if (cinfo->in_color_space == OCS_BGRA) {
            cconvert->pub.start_pass = rgb_ycc_start;
            cconvert->pub.color_convert = bgr_ycc_convert;
        }
		...
            
    }
    ...
        

The JPEG files are decompressed to RGB565 format only, no other formats are considered although the library has options for other color spaces.

jpeg.cpp - This is an additional file to the library to setup and control the features of the library code.

...
// Set output to 16bit 5-6-5 RGB format
// We can add support for other bit-depth in the future
...
cinfo.out_color_space = JCS_RGB;
cinfo.do_fancy_upsampling = FALSE;
...
   
----------------------------
/* Known color spaces. */

typedef enum {
   JCS_UNKNOWN,      /* error/unspecified */
   JCS_GRAYSCALE,      /* monochrome */
   JCS_RGB,      /* red/green/blue */
   JCS_YCbCr,      /* Y/Cb/Cr (also known as YUV) */
   JCS_CMYK,      /* C/M/Y/K */
   JCS_YCCK      /* Y/Cb/Cr/K */
   ,OCS_BGR      /* Win32 24bpp BI_RGB BGR (RGB reversed) */
   ,OCS_BGRA      /* Win32 32bpp BI_RGB BGRA (spurious byte) */
   ,OCS_PAL      /* Win32 palette */
    ,NANOCLR_RGB16      /* 16bpp RGB (5-6-5) */
} J_COLOR_SPACE;

How to reproduce

No response

Expected behaviour

No response

Screenshots

No response

Aditional information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions