/// Copyright (c) 2008 Jeffrey Powers for Fluxcapacity Open Source. /// Under the MIT License, details: License.txt. using System; #if SILVERLIGHT #else using System.Drawing; using System.Drawing.Imaging; #endif namespace FluxJpeg.Core { public struct ColorModel { public ColorSpace colorspace; public bool Opaque; } public enum ColorSpace { Gray, YCbCr, RGB } public class Image { private ColorModel _cm; private byte[][,] _raster; public byte[][,] Raster { get { return _raster; } } public ColorModel ColorModel { get { return _cm; } } /// X density (dots per inch). public double DensityX { get; set; } /// Y density (dots per inch). public double DensityY { get; set; } public int ComponentCount { get { return _raster.Length; } } /// /// Converts the colorspace of an image (in-place) /// /// Colorspace to convert into /// Self public Image ChangeColorSpace(ColorSpace cs) { // Colorspace is already correct if (_cm.colorspace == cs) return this; byte[] ycbcr = new byte[3]; byte[] rgb = new byte[3]; if (_cm.colorspace == ColorSpace.RGB && cs == ColorSpace.YCbCr) { /* * Y' = + 0.299 * R'd + 0.587 * G'd + 0.114 * B'd Cb = 128 - 0.168736 * R'd - 0.331264 * G'd + 0.5 * B'd Cr = 128 + 0.5 * R'd - 0.418688 * G'd - 0.081312 * B'd * */ for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) { YCbCr.fromRGB(ref _raster[0][x, y], ref _raster[1][x, y], ref _raster[2][x, y]); } _cm.colorspace = ColorSpace.YCbCr; } else if (_cm.colorspace == ColorSpace.YCbCr && cs == ColorSpace.RGB) { for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) { // 0 is LUMA // 1 is BLUE // 2 is RED YCbCr.toRGB(ref _raster[0][x, y], ref _raster[1][x, y], ref _raster[2][x, y]); } _cm.colorspace = ColorSpace.RGB; } else if (_cm.colorspace == ColorSpace.Gray && cs == ColorSpace.YCbCr) { // To convert to YCbCr, we just add two 128-filled chroma channels byte[,] Cb = new byte[width, height]; byte[,] Cr = new byte[width, height]; for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) { Cb[x, y] = 128; Cr[x, y] = 128; } _raster = new byte[][,] { _raster[0], Cb, Cr }; _cm.colorspace = ColorSpace.YCbCr; } else if (_cm.colorspace == ColorSpace.Gray && cs == ColorSpace.RGB) { ChangeColorSpace(ColorSpace.YCbCr); ChangeColorSpace(ColorSpace.RGB); } else { throw new Exception("Colorspace conversion not supported."); } return this; } private int width; private int height; public int Width { get { return width; } } public int Height { get { return height; } } public Image(ColorModel cm, byte[][,] raster) { width = raster[0].GetLength(0); height = raster[0].GetLength(1); _cm = cm; _raster = raster; } public static byte[][,] CreateRaster(int width, int height, int bands) { // Create the raster byte[][,] raster = new byte[bands][,]; for (int b = 0; b < bands; b++) raster[b] = new byte[width, height]; return raster; } delegate void ConvertColor(ref byte c1, ref byte c2, ref byte c3); #if SILVERLIGHT #else public Bitmap ToBitmap() { ConvertColor ColorConverter; switch(_cm.colorspace) { case ColorSpace.YCbCr: ColorConverter = YCbCr.toRGB; break; default: throw new Exception("Colorspace not supported yet."); } int _width = width; int _height = height; Bitmap bitmap = new Bitmap(_width, _height, PixelFormat.Format32bppArgb); BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); byte[] outColor = new byte[3]; byte[] inColor = new byte[3]; unsafe { int i = 0; byte* ptrBitmap = (byte*)bmData.Scan0; for (int y = 0; y < _height; y++) { for (int x = 0; x < _width; x++) { ptrBitmap[0] = (byte)_raster[0][x, y]; ptrBitmap[1] = (byte)_raster[1][x, y]; ptrBitmap[2] = (byte)_raster[2][x, y]; ColorConverter(ref ptrBitmap[0], ref ptrBitmap[1], ref ptrBitmap[2]); // Swap RGB --> BGR byte R = ptrBitmap[0]; ptrBitmap[0] = ptrBitmap[2]; ptrBitmap[2] = R; ptrBitmap[3] = 255; /* 100% opacity */ ptrBitmap += 4; // advance to the next pixel i++; // " } } } bitmap.UnlockBits(bmData); return bitmap; } #endif } }