From 2f2b94d8f454cbbdc4d2be64cd80255bfeaa232e Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 10 Jan 2023 12:16:30 +0100 Subject: [PATCH] Implement 16bit color decoding --- src/Aardvark.PixImage.Pfim.Tests/Dds.fs | 16 ++- src/Aardvark.PixImage.Pfim/PixImagePfim.fs | 120 +++++++++++++++++++-- 2 files changed, 121 insertions(+), 15 deletions(-) diff --git a/src/Aardvark.PixImage.Pfim.Tests/Dds.fs b/src/Aardvark.PixImage.Pfim.Tests/Dds.fs index 3801de7..31ecfc8 100644 --- a/src/Aardvark.PixImage.Pfim.Tests/Dds.fs +++ b/src/Aardvark.PixImage.Pfim.Tests/Dds.fs @@ -43,16 +43,16 @@ module DdsTests = [| 0; 28; 74 |] [| 189; 0; 16 |] [| 16; 174; 0 |] - [| 172; 161; 0 |] + [| 173; 162; 0 |] |] |> Array.map (Array.map uint8) let rgb5a1 = [| - [| 0; 32; 74; 255 |] + [| 0; 33; 74; 255 |] [| 189; 0; 16; 255 |] - [| 16; 172; 0; 255 |] - [| 172; 164; 0; 255 |] + [| 16; 173; 0; 255 |] + [| 173; 165; 0; 255 |] |] |> Array.map (Array.map uint8) @@ -252,11 +252,9 @@ module DdsTests = // Broken: Pfim parses as grayscale // testCase "Dds.R3G3B2 mipmapped" Cases.r3g3b2Mipmapped - // TODO: Implement us! - //testCase "Dds.R5G6B5 mipmapped" Cases.r5g6b5Mipmapped - //testCase "Dds.RGB5A1 mipmapped" Cases.rgb5a1Mipmapped - //testCase "Dds.RGBA4 mipmapped" Cases.rgba4Mipmapped - + testCase "Dds.R5G6B5 mipmapped" Cases.r5g6b5Mipmapped + testCase "Dds.RGB5A1 mipmapped" Cases.rgb5a1Mipmapped + testCase "Dds.RGBA4 mipmapped" Cases.rgba4Mipmapped testCase "Dds.L8 mipmapped" Cases.l8Mipmapped // Broken: Pfim parses as r5g5b5a1 diff --git a/src/Aardvark.PixImage.Pfim/PixImagePfim.fs b/src/Aardvark.PixImage.Pfim/PixImagePfim.fs index e850470..545c61a 100644 --- a/src/Aardvark.PixImage.Pfim/PixImagePfim.fs +++ b/src/Aardvark.PixImage.Pfim/PixImagePfim.fs @@ -2,6 +2,7 @@ open System open System.IO +open FSharp.NativeInterop open Pfim #nowarn "9" @@ -21,10 +22,41 @@ module private PixImagePfimImpl = if isNull x.MipMaps then 1 else x.MipMaps.Length + 1 + [] + module private ColorUtilities = + + module Bits = + + module Tables = + + let expand1 = + Array.init 2 (fun i -> uint8 (i * 255)) + + let expand4 = + Array.init 16 (fun i -> uint8 (i * 17)) + + let expand5 = + Array.init 32 (fun i -> uint8 ((i <<< 3) ||| (i >>> 2))) + + let expand6 = + Array.init 64 (fun i -> uint8 ((i <<< 2) ||| (i >>> 4))) + + let inline expand1 x = + Tables.expand1.[int x] + + let inline expand4 x = + Tables.expand4.[int x] + + let inline expand5 x = + Tables.expand5.[int x] + + let inline expand6 x = + Tables.expand6.[int x] + // Note: The default allocator of Pfim just allocates a new array, so // we can just use it without worrying about it being reused when the original // IImage is disposed. - let private of8Bit (format : Col.Format) (info : MipMapOffset) (src : uint8[]) = + let private of8Bit (format : Col.Format) (info : MipMapOffset) (src : uint8[]) : PixImage = let channels = format.ChannelCount() @@ -38,17 +70,93 @@ module private PixImagePfimImpl = ) ) - PixImage(format, volume) :> PixImage + PixImage(format, volume) + + let private of16Bit (format : Col.Format) (decode : uint16 -> C4b) (info : MipMapOffset) (src : uint8[]) : PixImage = + if info.Stride % 2 <> 0 then + failwith $"Stride is odd ({info.Stride})." + + let pi = PixImage(format, int64 info.Width, int64 info.Height) + + pinned src (fun src -> + let srcVolume = + NativeVolume( + NativePtr.ofNativeInt (src + nativeint info.DataOffset), + VolumeInfo( + 0L, + V3l(info.Width, info.Height, 1), + V3l(1, info.Stride / 2, 1) + ) + ) + + PixImage.pin pi (fun dst -> + let dstVolume = + NativeVolume( + dst.Pointer, + VolumeInfo(dst.Origin, dst.Size.XYI, dst.Delta) + ) + + (srcVolume, dstVolume) ||> NativeVolume.iterPtr2 (fun _ src dst -> + let color = decode <| NativePtr.read src + NativePtr.set dst 0 color.B + NativePtr.set dst 1 color.G + NativePtr.set dst 2 color.R + NativePtr.set dst 3 color.A + ) + ) + ) + + pi + + let private ofRGBA16 = + of16Bit Col.Format.BGRA (fun pixel -> + let b = pixel &&& 0xFus + let g = (pixel >>> 4) &&& 0xFus + let r = (pixel >>> 8) &&& 0xFus + let a = (pixel >>> 12) &&& 0xFus + C4b( + Bits.expand4 r, + Bits.expand4 g, + Bits.expand4 b, + Bits.expand4 a + ) + ) + + let private ofR5G5B5A1 (withAlpha : bool) = + of16Bit Col.Format.BGRA (fun pixel -> + let b = pixel &&& 0x1Fus + let g = (pixel >>> 5) &&& 0x1Fus + let r = (pixel >>> 10) &&& 0x1Fus + let a = (pixel >>> 15) &&& 0x1us + C4b( + Bits.expand5 r, + Bits.expand5 g, + Bits.expand5 b, + if withAlpha then Bits.expand1 a else 255uy + ) + ) + + let private ofR5G6B5 = + of16Bit Col.Format.BGR (fun pixel -> + let b = pixel &&& 0x1Fus + let g = (pixel >>> 5) &&& 0x3Fus + let r = (pixel >>> 11) &&& 0x1Fus + C4b( + Bits.expand5 r, + Bits.expand6 g, + Bits.expand5 b + ) + ) - // TODO: Implement missing formats let private levelToPixImageTable : (ImageFormat -> (MipMapOffset -> uint8[] -> PixImage) option) = LookupTable.lookupTable' [ ImageFormat.Rgb8, of8Bit Col.Format.Gray ImageFormat.Rgb24, of8Bit Col.Format.BGR ImageFormat.Rgba32, of8Bit Col.Format.BGRA - //ImageFormat.R5g5b5 - //ImageFormat.R5g6b5 - //ImageFormat.R5g5b5a1 + ImageFormat.Rgba16, ofRGBA16 + ImageFormat.R5g5b5, ofR5G5B5A1 false + ImageFormat.R5g6b5, ofR5G6B5 + ImageFormat.R5g5b5a1, ofR5G5B5A1 true ] let private levelToPixImage (format : ImageFormat) =