none
Creating Developer Defined BitmapPalette for 16-Bit Grayscale Image

    Question

  • Hi,
    Wondering, if someone else knows/can help?
     
    I am looking for a way to use a custom palette with 16-bit grayscale image data. I am looking to remap the pixels for display only (so can see normally image features that are too dark). I want to use the palette to avoid looping through all of the millions of pixels in the image (which does not meet my performance desires).
     
    This code displays the 16-bit grayscale image correctly.
                PixelFormat pf = PixelFormats.Gray16; //
                int rawStride = ((width * pf.BitsPerPixel) + 7) / 8;
     
                // Create a BitmapSource.
                BitmapPalette palette = BitmapPalettes.Gray16;
                BitmapSource bitmap = BitmapSource.Create(
                    width, 
                    height,
                    96, // 96 is dpi for X and Y
                    96, 
                    pf, 
                    palette, 
                    pixelData, 
                    rawStride);
     
                return bitmap;
     
    It seems to ignore a new custom palette (not in above code) unless I make the
    PixelFormat pf = PixelFormats.Indexed<4|8>
    but I do not see a PixelFormats.Indexed16 for 16-bit support.
     
    Thanks for looking!
    Sunday, December 23, 2012 5:27 PM

Answers

  • Gray16 is not an indexed format, palettes only work with indexed formats. I think the only way to do what you want in WPF without manipulating pixels directly is to use a ColorConvertedBitmap. Unfortunately that requires you to provide a ICC color profile so you'll have to make one to suit your requirements. Looks like the color.org site has a tool that's capable of editing existing ICC color profiles but I'm not familiar with it: http://www.color.org/profileinspector.xalter

    @Sheldon_Xiao: I'm not sure why you moved this to Visual C#, this is really a WPF question.

    • Marked as answer by Buck3 Thursday, December 27, 2012 2:56 AM
    Tuesday, December 25, 2012 8:31 AM
    Moderator
  • "A 4 bit image is definitely an indexed image.  The Gray16 bitmap palette contains 16 entries."

    Too bad that 4 bit != 16 bit. Maybe you should read the documentation before expediting answers like that.

    Palettes are not images. Read the OP.  The OP seems to be trying to construct an image with a 4 bit palette.

    I understand that it is easy to read one thing and comprehend something different.  He may also have a Gray16 image.  Perhaps he will clarify.  He seems to be on  the right track, but he needs to use FormatConvertedBitmap to convert the image to an indexed image with a custom palette. 


    • Edited by JohnWein Tuesday, December 25, 2012 10:16 AM
    • Marked as answer by Buck3 Thursday, December 27, 2012 2:57 AM
    Tuesday, December 25, 2012 10:15 AM
  • Remember, even though you are working with a 16-bit source, WPF will only display it with 256 discrete values. Even medical-grade monitors will only display 10-12 bits of gray.

    I just remapped (linearly) a 16-bit grayscale tif with 2048 x 2048 dimensions into an UInt16[]. On my 6-core AMD Phenom II (middle-of-the-road box, performance-wise) it took approx. 25 ms. not including the time to decode the image of course. 4096 x 4096 took approx. 75 ms. This should provide slider-interactive frame rates for you...that's what you're after right? Was your test code parallelized?

    -L


    • Edited by LKeene Thursday, December 27, 2012 1:58 AM
    • Marked as answer by Buck3 Sunday, December 30, 2012 9:28 PM
    Thursday, December 27, 2012 1:52 AM
  • "In the case of this class, what does IDisposable need to do. I am pretty fresh over from the Visual C++ area."

    Dispose should release all memory mapping related stuff:

    public void Dispose() {
        if (bgr32 != null) {
            sectionView.SafeMemoryMappedViewHandle.ReleasePointer();
            // make sure we don't release twice
            bgr32 = null;
        }
    
        if (sectionView != null)
            sectionView.Dispose();
    
        if (section != null)
            section.Dispose();
    }
    
    

    "What is the motivation for using memory mapped files in BitmapMapper?"

    The only way I know to have direct access to the pixels of a BitmapSource it to use CreateBitmapSourceFromMemorySection. This direct access allows you to avoid additional expensive pixel copies that are othewrise required when using something like WriteableBitmap. WriteableBitmap needs to do at least one copy, from the pixel array to unmanaged memory.

    Now, CreateBitmapSourceFromMemorySection documentation is bogus and claims that the first parameter is "A pointer to a memory section". It doesn't work with a pointer and the "memory section" name gives it away, it really expects a handle to a Win32 file mapping. Also, the equivalent WIC documentation is correct: "The section handle. This is a file mapping object returned by the CreateFileMapping function" - http://msdn.microsoft.com/en-us/library/ee719821(v=vs.85).aspx

     "What does this code do?"

    Well, you can PInvoke Win32 functions to create a file mapping or use the support provided by .NET for that. The code is the equivalent of CreateFileMapping/MapViewOfFile Win32 calls. Since you're coming from C++ maybe you're familiar with those.

    You may wonder why does this needs a memory section at all and why isn't a "simple" memory pointer obtained from some memory allocation function enough. I don't know and the documentation for this stuff is very thin but I suspect one reason is that this is because this way the bitmap retains ownership of the memory section and it still works even you release your memory section handle.

    "I suspect there is enough leeway in the time is takes for a slightly slower PC and this was in debug mode."

    Note that due to the large size of the bitmap the performance my also be affected by the GPU. Ultimately the image needs to be sent to a GPU texture. You may experiment with disabling hardware acceleration (set the ProcessRenderMode property of RenderOptions class to SoftwareOnly) and see how the performance is affected. On my machine software rendering turns out to be faster in this particular case.

    • Marked as answer by Buck3 Thursday, January 03, 2013 8:56 PM
    Wednesday, January 02, 2013 8:06 AM
    Moderator

All replies

  • Hi Buck3,

    I will move your thread to more approrate forum to get better support.

    Best regards,


    Sheldon _Xiao
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, December 25, 2012 5:09 AM
    Moderator
  • You have a 4 bit palette, so you have a Indexed4 pixel format.  Construct a 16 color BitmapPalette with the colors you want.
    Tuesday, December 25, 2012 6:11 AM
  • Gray16 is not an indexed format, palettes only work with indexed formats. I think the only way to do what you want in WPF without manipulating pixels directly is to use a ColorConvertedBitmap. Unfortunately that requires you to provide a ICC color profile so you'll have to make one to suit your requirements. Looks like the color.org site has a tool that's capable of editing existing ICC color profiles but I'm not familiar with it: http://www.color.org/profileinspector.xalter

    @Sheldon_Xiao: I'm not sure why you moved this to Visual C#, this is really a WPF question.

    • Marked as answer by Buck3 Thursday, December 27, 2012 2:56 AM
    Tuesday, December 25, 2012 8:31 AM
    Moderator
  • Hi Buck3,

    I am sorry that I do the wrong operation to your thread, it is a WPF thread, so I wll move it back to WPF forum, sorry for my mistake.

    @Mike Danes  Thank you.

    Best regards,


    Sheldon _Xiao
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, December 25, 2012 8:53 AM
    Moderator
  • Gray16 is not an indexed format, palettes only work with indexed formats. I think the only way to do what you want in WPF without manipulating pixels directly is to use a ColorConvertedBitmap. Unfortunately that requires you to provide a ICC color profile so you'll have to make one to suit your requirements. Looks like the color.org site has a tool that's capable of editing existing ICC color profiles but I'm not familiar with it: http://www.color.org/profileinspector.xalter

    @Sheldon_Xiao: I'm not sure why you moved this to Visual C#, this is really a WPF question.

    A 4 bit image is definitely an indexed image.  The Gray16 bitmap palette contains 16 entries.
    Tuesday, December 25, 2012 9:01 AM
  • "A 4 bit image is definitely an indexed image.  The Gray16 bitmap palette contains 16 entries."

    Too bad that 4 bit != 16 bit. Maybe you should read the documentation before expediting answers like that.

    Tuesday, December 25, 2012 9:43 AM
    Moderator
  • "A 4 bit image is definitely an indexed image.  The Gray16 bitmap palette contains 16 entries."

    Too bad that 4 bit != 16 bit. Maybe you should read the documentation before expediting answers like that.

    Palettes are not images. Read the OP.  The OP seems to be trying to construct an image with a 4 bit palette.

    I understand that it is easy to read one thing and comprehend something different.  He may also have a Gray16 image.  Perhaps he will clarify.  He seems to be on  the right track, but he needs to use FormatConvertedBitmap to convert the image to an indexed image with a custom palette. 


    • Edited by JohnWein Tuesday, December 25, 2012 10:16 AM
    • Marked as answer by Buck3 Thursday, December 27, 2012 2:57 AM
    Tuesday, December 25, 2012 10:15 AM
  • Can you provide an idea of what sort of performance you're after? If you want to linearly remap the pixels within new clipping limits, this is a very fast...almost trivial... operation for modern multicore CPUs. Real time in most cases (depending on the image size of course).

    -L

    Tuesday, December 25, 2012 5:14 PM
  • "Palettes are not images."

    Nobody said that they are.

    "The OP seems to be trying to construct an image with a 4 bit palette."

    The OP states very clearly what he wants: "I am looking to remap the pixels for display only (so can see normally image features that are too dark). "

    "He seems to be on  the right track, but he needs to use FormatConvertedBitmap to convert the image to an indexed image with a custom palette."

    Might work but probably the quality will suck because FormatConvertedBitmap forces dithering when converting to indexed formats.

    Wednesday, December 26, 2012 12:23 AM
    Moderator
  • Hello All,

    Thanks for all of the responses. I was bad and took Christmas off :).

    I like the ideas of the ColorConvertedBitmap (Mike Danes) and FormatConvertedBitmap (JohnWein). Unless, I hear something else, I will pursue both of these. Hopefully going to a 256 entry palette will not be too bad for display quality (unless someone knows of how to get a bigger indexed palette). Since I am fairly new to these classes, I hope I can find some good examples. I expect to respond in a couple days (time to look into these).

    To clarify, this is for 16-bit images - those which contain 65536 shades of gray. Using PixelFormats.Gray16 allowed me to load the image to be viewed correctly, but I am having trouble seeing dark pixels/image areas and would like a way to brighten them for display only. I say display only since the images are 6+ MB in size and I would prefer not to go through all pixels changing them as a slider is moved to get the best displayed result. It "seems" like that is a good job for the image palette. That said, I did do some initial tests in debug mode with unsafe regions changing all of the pixels and that did not look promising from a performance standpoint (thanks LKeene for the question!). Also, I would "like" to avoid duplication of the image data: one for algorithms when need to read original pixel values and another for display. I can compromise on the duplication of data desire if needed (potential direction headed anyway).

    Sheldon _Xiao - thanks for moving my question around. The extra exposure helped!

    If there are any outstanding questions, please let me know.

    Thanks

    Wednesday, December 26, 2012 10:57 PM
  • "Hopefully going to a 256 entry palette will not be too bad for display quality"

    It should be good enough, anyway most consumer LCDs can't display more than 256 shades of gray. And AFAIR even professional LCDs don't display more than 1024 shades of gray.

    Wednesday, December 26, 2012 11:11 PM
    Moderator
  • Your 16 bit images are displayed as BGRA32 images with 8 bits per channel.  That is they are displayed as 8 bit gray scale images.  You can achieve what you want by increasing the contrast, which multiplies each pixel value by a constant.  Consider multiplying each pixel's value by 2.  Each pixel with a value less than 128 is doubled and each pixel with a value >= 128 gets the value 255.  The contrast can be varied by the rendering engine without changing the pixels.
    • Edited by JohnWein Thursday, December 27, 2012 2:20 AM
    Wednesday, December 26, 2012 11:15 PM
  • Remember, even though you are working with a 16-bit source, WPF will only display it with 256 discrete values. Even medical-grade monitors will only display 10-12 bits of gray.

    I just remapped (linearly) a 16-bit grayscale tif with 2048 x 2048 dimensions into an UInt16[]. On my 6-core AMD Phenom II (middle-of-the-road box, performance-wise) it took approx. 25 ms. not including the time to decode the image of course. 4096 x 4096 took approx. 75 ms. This should provide slider-interactive frame rates for you...that's what you're after right? Was your test code parallelized?

    -L


    • Edited by LKeene Thursday, December 27, 2012 1:58 AM
    • Marked as answer by Buck3 Sunday, December 30, 2012 9:28 PM
    Thursday, December 27, 2012 1:52 AM
  • Hi LKeene, In debug mode, I am getting 844 ms to loop over the array modifying all image pixels. The image width is 3352 and height of 2532. I could speed it up further by simpifying and putting as much math functionality outside of the loop. I have not parallelized, but am interested. Do you have any samples you could share or in general how you went about it? I am looking at the palette method to see how it compares. LKeene - do you see any advantages over this (i.e. direct pixel manipulation) compared to using the palette? It seems with either method a duplicate image copy is needed (for algorithm purposes of accessing the raw 16-bit data values unless I am missing something) and looping through a palette seems quicker without having to be concerned with optimizations? <Edit of 12/28/2012 at 6:54PM Pacific: deleted code due to formatting issue plus small edits/clarifications. Also note that I expect to respond to this thread on Monday. Thanks to all for the responses.> Best and thanks again, Buck
    • Edited by Buck3 Saturday, December 29, 2012 2:58 AM Code formatting issues plus clarifications.
    Friday, December 28, 2012 5:51 PM
  • "It seems with either method a duplicate image copy is needed"

    Maybe you can simply modify the pixels while copying them from your pixelData to a WritableBitmap so you don't need any intermediary bitmaps?

    "looping through a palette seems quicker without having to be concerned with optimizations?"

    Depends. Your code is faster but WPF doesn't display indexed images directly. It always converts to a non indexed format. So every time you change the palette WPF will have to convert the bitmap.

    "deleted code due to formatting issue plus small edits/clarifications"

    You can use the 'Insert Code Block' button (the one between HTML and Insert Image") to insert code in your post.

    I've seen the code before you deleted it, you're using Math.Pow. Since the number of colors is relatively small (<=65536) it may be faster if you build and use a lookup table (an array with 65536 entries). Indexing into an array has it's cost but it's probably faster than dealing with floating point and Math.Pow.

    Saturday, December 29, 2012 7:56 AM
    Moderator
  • Math.Pow? It doesn't sound like you are applying a linear remapping. Are you trying to apply some sort of gamma encoding? The Pow function will certainly take longer than a simple linear normalization. Are you actually trying to perform a linear histogram stretch between two 16-bit values? In other words, if you have a dark region of your image, do you want to remap those values to the full 16-bit range in a linear fashion?

    Also, what type of machine are you testing on?

    Sunday, December 30, 2012 1:55 AM
  • "In other words, if you have a dark region of your image, do you want to remap those values to the full 16-bit range in a linear fashion?"

    That is do you want to adjust the brightness and contrast?

    Sunday, December 30, 2012 2:58 AM
  • Hello LKeene, John, and Mike,

    Great progress thanks to your help! If I can determine how to get quicker updates to the image control and optimize brightening code a bit more I will be set. For this thread/original question, the optimization of the brightening code is the most pertinent (but any insights appreciated). The code which brightens the image is taking ~1/3 the time and 2/3 the time is to other tasks (e.g. updating of the image). Timing information is below the code.

    I am focusing on LKeene's suggestion of modifying the pixels over the palette (based on Mike's additional comments and the challenges/limitations I see with the palettes). This assumes changing the pixels can be made quick enough. I am running on a Intel i5 CPU @ 3.2GHz with 6GB of memory. I expect users will be running computers as slow as they can get away with (e.g. laptop/PII). I plan on consequently adding a way that image brightening updates can (optionally) occur after a slider drag is completed. So far, one big improvement was creating the lookup table as Mike suggested. Another was removing an extra copy of the bitmap which was not needed.

    Yes, I am doing a non-linear stretch so darker pixels will be more pronounced versus a linear histogram stretch. John - you are right. I guess I am attempting to do a simple all in one display processing of the image versus separate controls for brightness, contrast, curves, gamma, etc. It is a pretty simple application in this regard since more should not be required.

    The code which could use optimization is below (using "'Insert Code Block' button"-thanks!). Mike - would you mind letting me know if you think the "WritableBitmap" suggestion looks worthwhile compared to this implementation and the timings below? Throughout the application, I am using BitmapSource as my primary means of storing and displaying the image.

            /// <summary>
            /// Creates new bitmap source based on brightening amount.
            /// </summary>
            /// <param name="bitmapSource">The source bitmap source.</param>
            /// <param name="amount">The brightening amount.</param>
            /// <returns>
            /// The resulting image applying the brightening amount.
            /// </returns>
            public static BitmapSource CreateBrighterBitmap(BitmapSource bitmapSource, double amount)
            {
                // Start stopwatch that shows overall time
                var overallTimer = Stopwatch.StartNew();
                var taskTimer = Stopwatch.StartNew();
    
                 // Create light and dark pixel arrays for math operation (pixel by pixel)
                 ushort[] bitmapPixelData = bitmapSource.CreatePixelDataArray();
                
                // Create lookup table
                ushort[] lookupTable = new ushort[ushort.MaxValue];
                Debug.WriteLine("Time to create needed objects: {0}ms", taskTimer.ElapsedMilliseconds);
    
                // Use unsafe to quickly loop through all image values subtracting the dark
                int pixelWidth = bitmapSource.PixelWidth; // a.k.a. image width
                int pixelHeight = bitmapSource.PixelHeight;
                unsafe
                {
                    // Add entries to lookup table
                    fixed (ushort* table = lookupTable)
                    {
                        taskTimer = Stopwatch.StartNew();
                        for (int i = 0; i < lookupTable.Length; i++)
                        {
                            // Round and make sure entries do not exceed maximum value 
                            table[i] = (ushort)Math.Round(Math.Min((double)Math.Pow(i, amount), ushort.MaxValue));
                        }
    
                        Debug.WriteLine("Time to create lookup table: {0}ms", taskTimer.ElapsedMilliseconds);
    
                        // Lookup values in lookup Table using non-linear scaling 
                        fixed (ushort* pixelData = bitmapPixelData)
                        {
                            taskTimer = Stopwatch.StartNew();
                            for (int i = 0; i < bitmapPixelData.Length; i++)
                            {
                                pixelData[i] = table[pixelData[i]];
                            }
    
                            Debug.WriteLine("Time to loop through pixel data array making it brighter is {0}ms for a width of {1} and height of {2}.", taskTimer.ElapsedMilliseconds, pixelWidth, pixelHeight);
                        }
                    }
                }
    
                // Write updated pixels to bitmap
                taskTimer = Stopwatch.StartNew();
                //bitmap.WritePixels(new Int32Rect(0, 0, width, height), bitmapPixelData, widthInByte, 0);
    
                // Create a new bitmap source for returning
                BitmapSource bitmapResult = ImageHelper.CreateBitmapSource(pixelWidth, pixelHeight, bitmapPixelData);
                Debug.WriteLine("Time to create new bitmap source with updated pixel values: {0}ms", taskTimer.ElapsedMilliseconds);
                
                // Display how long to brighten new bitmap source
                Debug.WriteLine("Total time to create brighter bitmap source: {0}ms", overallTimer.ElapsedMilliseconds);
    
                return bitmapResult;
            }
            /// <summary>
            /// Creates the pixel data array.
            /// </summary>
            /// <param name="bitmap">The source bitmap.</param>
            /// <param name="height">The height.</param>
            /// <param name="width">The width.</param>
            /// <param name="widthInByte">The width in bytes.</param>
            /// <returns>The resulting pixel data array.</returns>
            public static ushort[] CreatePixelDataArray(this BitmapSource bitmap)
            {
                // Create bitmap pixel array
                int height = bitmap.PixelHeight;
                int width = bitmap.PixelWidth;
                var bitmapPixelData = new ushort[width * height];
    
                // Copy the pixels
                int widthInByte = sizeof(ushort) * width;
                bitmap.CopyPixels(bitmapPixelData, widthInByte, 0);
                
                return bitmapPixelData;
            }



            /// <summary>
            /// Creates the pixel data array.
            /// </summary>
            /// <param name="bitmap">The source bitmap.</param>
            /// <param name="height">The height.</param>
            /// <param name="width">The width.</param>
            /// <param name="widthInByte">The width in bytes.</param>
            /// <returns>The resulting pixel data array.</returns>
            public static ushort[] CreatePixelDataArray(this BitmapSource bitmap)
            {
                // Create bitmap pixel array
                int height = bitmap.PixelHeight;
                int width = bitmap.PixelWidth;
                var bitmapPixelData = new ushort[width * height];
    
                // Copy the pixels
                int widthInByte = sizeof(ushort) * width;
                bitmap.CopyPixels(bitmapPixelData, widthInByte, 0);
                
                return bitmapPixelData;
            }

    Timing output is as follows (debug mode):

    Time to create needed objects: 54ms
    Time to create lookup table: 6ms
    Time to loop through pixel data array making it brighter is 61ms for a width of 3352 and height of 2532.
    Time to create new bitmap source with updated pixel values: 14ms
    Total time to create brighter bitmap source: 163ms

    I am amazed with how fast creating the lookup table is!

    The overall update rate if dragging the slider is twice per second (approximately) taking into account image display (which is of course not in timing displayed). The extra ~350ms I need to see where it is going (e.g. image update) in debug mode. I tried explicitly refreshing the controls, but no speedup. Release build gives it ~3 updates / second (as an estimate). This is a MVVM application so I use binding. I will keep investigating this in the background.

    Thanks for the latest round of information/feedback. Feel free to "blast" the code. Your suggestions have been worthwhile/appreciated. Any reminders of something mentioned but I did not address?

    Thanks again,

    Buck

    Sunday, December 30, 2012 9:25 PM
  • Hi Buck. You can try threading the LUT creation and image modification by using the thread pool as follows:


    using System.Threading;
    
    // Some global scope objects:
    AutoResetEvent ProcessingCompleteEvent = new AutoResetEvent(false);
    Int32 SyncVar;
    Int32 CurrentNumberOfWorkers;
    
    
    UInt16[] lookupTable = new UInt16[UInt16.MaxValue];
    
    // Initialize for LUT creation:
    SyncVar = -1;
    Int32 NumberOfThreadLaunched = Environment.ProcessorCount;
    CurrentNumberOfWorkers = NumberOfThreadsLaunched;
    Object[] threadArguments = new Object[3];
    threadArguments[0] = lookupTable;
    threadArguments[1] = NumberOfThreadsLaunched;
    
    // Assign work to thread pool:
    for(Int32 x = 0; x < NumberOfThreadsLaunched; x++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(BuildLUTThreadFunction), threadArguments);
    }
    
    // Wait for threads to complete:
    ProcessingCompleteEvent.WaitOne();
    
    // Reset thread variables:
    CurrentNumberOfWorkers = NumberOfThreadLaunched;
    SyncVar = -1;
    
    // Assuming the pixel data is contained in PixelArray:
    threadArguments[2] = PixelArray; // where "PixelArray" = UInt16[].
    
    // Modify image:
    for(Int32 x = 0; x < NumberOfThreadsLaunched; x++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(ModifyImageThreadFunction), threadArguments);
    }
    
    // Wait for threads to complete:
    ProcessingCompleteEvent.WaitOne();
    
    
    
    
    
    // The thread function for building the LUT:
    private void BuildLUTThreadFunction(Object state)
    {
        // Acquire thread arguments:
        Object[] arguments = (Object[])state;
        UInt16[] lut = (UInt16[])arguments[0];
        Int32 NumThreadsLaunched = (Int32)arguments[1];
    
        // Self-initialize:
        Int32 CurrentEntry = Interlocked.Increment(ref SyncVar);
    
        while(CurrentEntry < lut.Length)
        {
            lut[CurrentEntry] = (UInt16)Math.Round(Math.Min((Double)Math.Pow(CurrentEntry, amount), UInt16.MaxValue));
    
            CurrentEntry += NumThreadsLaunched;
        }
    
        // Check if this is the last thread to finish:
        if(Interlocked.Decrement(ref CurrentNumberOfWorkers) == 0)
        {
            // Alert UI thread:
            ProcessingCompleteEvent.Set();
        }
    }
    
    
    // Thread function for image modification:
    private void ModifyImageThreadFunction(Object state)
    {
        // Acquire thread arguments:
        Object[] arguments = (Object[])state;
        UInt16[] lut = (UInt16[])arguments[0];
        Int32 NumThreadsLaunched = (Int32)arguments[1];
        UInt16[] Pixels = (UInt16[])arguments[2];
    
        // Self-initialize:
        Int32 CurrentPixel = Interlocked.Increment(ref SyncVar);
    
        while(CurrentPixel < Pixels.Length)
        {
            Pixels[CurrentPixel] = lut[Pixels[CurrentPixel]];
    
            CurrentPixel += NumThreadsLaunched;
        }
    
        // Check if this is the last thread to finish:
        if(Interlocked.Decrement(ref CurrentNumberOfWorkers) == 0)
        {
            // Alert UI thread:
            ProcessingCompleteEvent.Set();
        }
    }

    Once the (pre-allocated) UInt16[] pixel array has been modified, copy it into a WriteableBitmap object that's been assigned to the "Source" property of an "Image" control.. On an i5 that sucker should scream. The release mode is probably 2X faster than debug mode.

    -L





    • Edited by LKeene Monday, December 31, 2012 4:02 AM
    Sunday, December 30, 2012 11:59 PM
  • "Mike - would you mind letting me know if you think the "WritableBitmap" suggestion looks worthwhile compared to this implementation and the timings below? "

    I did some tests and it looks like WriteableBitmap is faster but only if it uses a pixel format like Bgr32. This is perhaps not surprising because Gray16 is not a format that can be displayed directly, somewhere WPF has to convert it to Bgr32/Bgra32. If you use Bgr32 you have to convert yourself from Gray16 to Bgr32 which is quite OK because this can be done during linear mapping at no cost.

    You may point out that using Bgr32 means that you'll only be able to display 256 shades of gray instead of 65536 but as I mentioned above there are no LCDs that can display 65536 shades. There are some professional LCDs that can display 1024 shades but I have no idea if WPF supports that, there's a Bgr101010 pixel format but I don't have a graphics card that supports it for output and I can't test.

    Interestingly the timings when using Writeable bitmap are not very different from yours (I get 120 ms (total) for your code and 80 ms for WritableBitmap on a Core 2 Duo 2.8GHz) but the UI is far more responsive.

    Here's the complete code I used for WriteableBitmap. In the interest of simplicity I'm not using unsafe code, it doesn't make a difference in my measurements anyway.

    using System;
    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    
    namespace WpfApplication1 {
        class BitmapMapper {
            private WriteableBitmap bitmap;
            private int width;
            private int height;
            private ushort[] gray16;
            private uint[] bgr32;
            private uint[] lut;
    
            public BitmapMapper() {
                width = 3352;
                height = 2532;
                gray16 = new ushort[width * height];
    
                for (int y = 0; y < height; ++y) {
                    for (int x = 0; x < width; ++x)
                        gray16[y * width + x] = (ushort)(y * 25);
                }
    
                bgr32 = new uint[width * height];
                lut = new uint[ushort.MaxValue + 1];
                bitmap = new WriteableBitmap(width, height, 96.0, 96.0, PixelFormats.Bgr32, null);
            }
    
            public WriteableBitmap Bitmap {
                get {
                    return bitmap;
                }
            }
    
            public double Update(double amount) {
                var overallTimer = Stopwatch.StartNew();
    
                UpdateLut(amount);
                Map();
    
                bitmap.Lock();
                bitmap.WritePixels(new Int32Rect(0, 0, width, height), bgr32, width * 4, 0);
                bitmap.Unlock();
    
                return overallTimer.ElapsedMilliseconds;
            }
    
            private void UpdateLut(double amount) {
                for (int i = 0; i < lut.Length; i++) {
                    uint value = Math.Min(ushort.MaxValue, (uint)Math.Round(Math.Pow(i, amount)));
                    value >>= 8;
                    lut[i] = value | (value << 8) | (value << 16);
                }
            }
    
            private void Map() {
                for (int i = 0; i < gray16.Length; i++)
                    bgr32[i] = lut[gray16[i]];
            }
        }
    
        partial class MainWindow : Window {
            private BitmapMapper mapper = new BitmapMapper();
    
            public MainWindow() {
                InitializeComponent();
                image.Source = mapper.Bitmap;
            }
    
            private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
                info.Text = mapper.Update(e.NewValue).ToString();
            }
        }
    }
    

    <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525">
        <DockPanel>
            <Slider x:Name="slider"
                    DockPanel.Dock="Top"
                    Minimum="1" Maximum="2"
                    ValueChanged="slider_ValueChanged"/>
            <TextBlock x:Name="info"
                       DockPanel.Dock="Bottom" />
            <Image x:Name="image"/>
        </DockPanel>
    </Window>
    
    You may also consider using threading as LKeene shows but in my experience that won't make a difference in this case. The mapping code is extremly simple but accesses a lot of memory, it's likely that it's memory bandwidth bound rather than CPU bound and as a result using multiple threads is unlikely to improve performance.
    Monday, December 31, 2012 10:52 AM
    Moderator
  • I did some profiling out of curiosity and discovered that WriteableBitmap has its problems and it's possible to do even better than in my previous post. WriteableBitmap appears to do 2 additional bitmap copies and that costs about 50% of the total time. InteropBitmap works better:

    unsafe class BitmapMapper {
        private MemoryMappedFile section;
        private MemoryMappedViewAccessor sectionView;
        private InteropBitmap interop;
        private uint* bgr32;
        private int width;
        private int height;
        private ushort[] gray16;
        private uint[] lut;
    
        public BitmapMapper() {
            width = 3352;
            height = 2532;
            gray16 = new ushort[width * height];
    
            for (int y = 0; y < height; ++y)
            {
                for (int x = 0; x < width; ++x)
                    gray16[y * width + x] = (ushort)(y * 25);
            }
    
            section = MemoryMappedFile.CreateNew(null, width * height * 4);
            sectionView = section.CreateViewAccessor();
            byte* ptr = null;
            sectionView.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
            bgr32 = (uint*)ptr;
    
            interop = (InteropBitmap)Imaging.CreateBitmapSourceFromMemorySection(
                section.SafeMemoryMappedFileHandle.DangerousGetHandle(), width, height, PixelFormats.Bgr32, width * 4, 0);
    
            lut = new uint[ushort.MaxValue + 1];
        }
    
        public BitmapSource Bitmap {
            get {
                return interop;
            }
        }
    
        public double Update(double amount) {
            var overallTimer = Stopwatch.StartNew();
    
            UpdateLut(amount);
            Map();
    
            interop.Invalidate();
    
            return overallTimer.ElapsedMilliseconds;
        }
    
        private void UpdateLut(double amount) {
            // same code as in the previous post...
        }
    
        private void Map() {
            fixed (ushort* pGray16 = gray16)
            fixed (uint* pLut = lut)
                Map(pGray16, pLut, bgr32, gray16.Length);
        }
    
        private static void Map(ushort* gray16, uint* lut, uint* bgr32, int length) {
            var bgr32End = bgr32 + length;
    
            while (bgr32 < bgr32End)
                *bgr32++ = lut[*gray16++];
        }
    }
    
    It's a bit more messy due to the memory mapped section (and needs to implement IDisposable too!) but this one does the job in ~30ms instead of 80. There's almost no update lag in the UI. I ended up using pointers for Map because it's too slow to access the memory mapped section via the view accessor. Also, it seems that once the overhead from WriteableBitmap is gone those pointer optimizations start to be measurable (about 10ms).
    Monday, December 31, 2012 1:10 PM
    Moderator
  • Mike and LKeene,

    Thanks for all of the good work! I will respond by Wednesday. I need to timeshare a bit with some other tasks, but this is more enjoyable. ~30ms sounds amazing as well as the multiple threads. More to follow. I am looking forward to this!!

    Thanks again,

    Buck

    Tuesday, January 01, 2013 12:44 AM
  • Hi Mike, LKeene, and John,

    Okay, first of all and most importantly, happy new year!

    For the code, I have gone through the BitmapMapper, converted over to my project (very minor effort) and timed. It is amazing coming in at ~57ms per update for the larger and one smaller image (400x400 pixels) combined. The slider action is smooth and the image updates appear fluid. I suspect there is enough leeway in the time is takes for a slightly slower PC and this was in debug mode. Mike - Thank you!

    Next, I really like the idea of multiple threads but considering my lack of competency in that area it may adversely impact my integrating it into BitmapMapper. I have more to learn on this area. LKeene, thank you for all of the effort on this! I look forward to doing more in using multiple threads and your code looks like a great example. Overall, your solution sounds just as feasible as the BitmapMapper.

    For wrap up, I need to understand/investigate code from Mike. Specifically what these are.

    1. In the case of this class, what does IDisposable need to do. I am pretty fresh over  from the Visual C++ area.
    2.  What is the motivation for using memory mapped files in BitmapMapper? What does this code do?
    section = MemoryMappedFile.CreateNew(null, width * height * 4);
    
            sectionView = section.CreateViewAccessor();
    
            byte* ptr = null;
    
            sectionView.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
    
            bgr32 = (uint*)ptr;
    
     
    
            interop = (InteropBitmap)Imaging.CreateBitmapSourceFromMemorySection(
    
                section.SafeMemoryMappedFileHandle.DangerousGetHandle(), width, height, PixelFormats.Bgr32, width * 4, 0); 

    Thanks again for all of your work! I am very happy with what I learned in our discussions and the final solution.

    Buck


    Wednesday, January 02, 2013 3:47 AM
  • "In the case of this class, what does IDisposable need to do. I am pretty fresh over from the Visual C++ area."

    Dispose should release all memory mapping related stuff:

    public void Dispose() {
        if (bgr32 != null) {
            sectionView.SafeMemoryMappedViewHandle.ReleasePointer();
            // make sure we don't release twice
            bgr32 = null;
        }
    
        if (sectionView != null)
            sectionView.Dispose();
    
        if (section != null)
            section.Dispose();
    }
    
    

    "What is the motivation for using memory mapped files in BitmapMapper?"

    The only way I know to have direct access to the pixels of a BitmapSource it to use CreateBitmapSourceFromMemorySection. This direct access allows you to avoid additional expensive pixel copies that are othewrise required when using something like WriteableBitmap. WriteableBitmap needs to do at least one copy, from the pixel array to unmanaged memory.

    Now, CreateBitmapSourceFromMemorySection documentation is bogus and claims that the first parameter is "A pointer to a memory section". It doesn't work with a pointer and the "memory section" name gives it away, it really expects a handle to a Win32 file mapping. Also, the equivalent WIC documentation is correct: "The section handle. This is a file mapping object returned by the CreateFileMapping function" - http://msdn.microsoft.com/en-us/library/ee719821(v=vs.85).aspx

     "What does this code do?"

    Well, you can PInvoke Win32 functions to create a file mapping or use the support provided by .NET for that. The code is the equivalent of CreateFileMapping/MapViewOfFile Win32 calls. Since you're coming from C++ maybe you're familiar with those.

    You may wonder why does this needs a memory section at all and why isn't a "simple" memory pointer obtained from some memory allocation function enough. I don't know and the documentation for this stuff is very thin but I suspect one reason is that this is because this way the bitmap retains ownership of the memory section and it still works even you release your memory section handle.

    "I suspect there is enough leeway in the time is takes for a slightly slower PC and this was in debug mode."

    Note that due to the large size of the bitmap the performance my also be affected by the GPU. Ultimately the image needs to be sent to a GPU texture. You may experiment with disabling hardware acceleration (set the ProcessRenderMode property of RenderOptions class to SoftwareOnly) and see how the performance is affected. On my machine software rendering turns out to be faster in this particular case.

    • Marked as answer by Buck3 Thursday, January 03, 2013 8:56 PM
    Wednesday, January 02, 2013 8:06 AM
    Moderator
  • Thanks Mike for a great explanation and the final solution! The explanation makes sense. Much appreciated - Buck
    Thursday, January 03, 2013 8:56 PM