0

I am wanting to be able to iterate over a bitmap image and increase the value by one, but currently the image comes back from the memory stream a poorer quality than before and the change is not smooth at all.

    private static Bitmap CyclePalette(Bitmap original)
    {
        using (var bitMapStream = new MemoryStream())
        {
            original.Save(bitMapStream, format: ImageFormat.Gif);
            var newBitmap = new Bitmap(bitMapStream);

            var newPalette = newBitmap.Palette;

            for (var i = 0; i < newPalette.Entries.Length - 5; i++)
            {

                var oldColor = newPalette.Entries[i];
                newPalette.Entries[i] = newPalette.Entries[i + 1];
                newPalette.Entries[i + 1] = oldColor;

            }
            newBitmap.Palette = newPalette;
            return newBitmap; 
        }
    }
TaW
  • 53,122
  • 8
  • 69
  • 111
Stacker-flow
  • 1,251
  • 3
  • 19
  • 39
  • How would I use Bitmap.Clone() to do what I want? – Stacker-flow Nov 04 '14 at 18:55
  • 1
    Also, GIF [generally only supports 256 colors](http://en.wikipedia.org/wiki/Graphics_Interchange_Format#Palettes). If your bitmap has more, that probably explains your problem. Something like [MemoryBmp](http://msdn.microsoft.com/en-us/library/system.drawing.imaging.imageformat.memorybmp%28v=vs.90%29.aspx) or maybe [Png](http://msdn.microsoft.com/en-us/library/system.drawing.imaging.imageformat.png%28v=vs.90%29.aspx) would be better. – dbc Nov 04 '14 at 19:01
  • OP said *Iterate over image pixels* but has increased their index value by moving the palette assignments down one place, not by altering the colours themselves. Unless the palette is arranged in a perfect visually close sequence, the image will degrade. If the palette sequence is random, the image will become visual nonsense. – Weather Vane Nov 04 '14 at 19:17
  • Ok, so maybe going about using the palette to do what I want is wrong. How should I go about incrementing each pixel? – Stacker-flow Nov 04 '14 at 19:20
  • In this case, it won't make any difference as the pixel indexes the palette. Why not try, for example, `if (red < 255) red++;` for each of the rgb components of each palette entry? In the case of a bitmap with the rgb components coded as 24 or 32 bit pixel values, do the same thing with the pixel values as there is no palette. – Weather Vane Nov 04 '14 at 19:22
  • What are you trying to do? Are you cycling pixels between colors of a bitmap with indexed colors, or are you brightening an image by increasing the RGB values by 1 for each pixel? Your method name suggests the latter but your question title suggests the former. – dbc Nov 04 '14 at 19:24
  • I have a graphic that has been drawn on a picture box as a Bitmap (mandlebrot) I am trying to cycle through the pixels and change the colors. The above is close to what I want, but its a bit slow and the image is a little degraded after it has been switched. – Stacker-flow Nov 04 '14 at 19:27
  • What format does the Bitmap have? Is it really palette-based? I rotate colors in my 24bbp fractals by setting each pixel in a lockbits loop.. – TaW Nov 04 '14 at 19:48
  • The mandelbrot is a Bitmap, I used the palette looping as above when looking on another SO question on how to best do it. Although it does change the colours it makes it very grainy, I am not sure on how to use lockbits – Stacker-flow Nov 04 '14 at 19:51
  • 1
    In that case, construct a palette that changes the rgb values in a smoothe sequence, reserving some for special areas (where the iteration is 'infinite', as you appear to have done). The palette rotation will be quicker if instead of using a loop, you use `memcpy()` to shift the bulk of the palette. But the slowness might be due to offering the entire image to the rendering engine, rather than just the palette. – Weather Vane Nov 04 '14 at 19:53
  • I see no reason to always re-create the bitmap instead of just changing the palette. – TaW Nov 04 '14 at 19:54
  • Just another note on fractals: Palette based images are restricted to 256 colors, which is not a lot, considering how many Ks you have calculated, thousands probably. So the right way is to store the Ks in a file containnig the 2D array of int and to paint the images from these using a nice large palette of colors.. - color cycling then is simply achieved by changing the mapping into the pallete. I can paint a 500x500 bitmap with thousands of colors at 30fps on even just one core of my machine, using lockbits.. – TaW Nov 04 '14 at 21:13
  • @TaW Could you show an example? I haven't used lockbits before, and finding it hard to get my head round. – Stacker-flow Nov 04 '14 at 21:27
  • I'll try to post a little something tomorrow. Do you do the calculating yourself? – TaW Nov 04 '14 at 21:29
  • Yes, using a pretty standard way. – Stacker-flow Nov 04 '14 at 21:30
  • 1
    I have added an explanation why your way can't work well and a solution that works very nicely for me.. – TaW Nov 05 '14 at 07:10
  • 1
    I hope you like the solution; you may still be interested in the update. I was wrong about GDI crashing on the undithered GIF file; my file had a wrong extension.. So I have added code to quickly rortate a GIF palette. But it will always look poor compared to the solution in PaintKs.. – TaW Nov 05 '14 at 21:23

2 Answers2

1

First a word about GIF files and your original solution.

It is pretty simple to make it run real fast, but when, for fun, I did just that I had to laugh - now I know what you meant by the quality degraded..!!

Let me explain: There are a few options in GIF files, but here the important one is whether it is dithered or not.

I have already mentioned that they have only 256 colors; to look nice, which they can, at least from a distance, the normal GIF files use a trick: They dither blocks of pixels to show a mix of colors! This works pretty well, but it completely prohibits to use the palette for color rotation..

Of course one can switch off dithering but the result not only looks pretty coarse and pixelated; with a suitable palette it is possible to do the rotation but the result is mediocre at best. I have appended code to do that in the function palRotate.

Which leaves us with my original suggestion: Forget GIF and go for for ARGB-color rotation! This also allows you to work with the full range of Ks you have calculated.. But you need tight code to make it run fast.

Here is a complete testbed using LockBits to load data and do full color cycling with them. You need to add a PictureBox, a Button and a Timer to your form. Note that you need to keep the size of the data equal to the size of the PictureBox. I have included a 500x500 testdata file.

using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.IO;
//..

// the data array
int[,] Ks;
// the offset for cycling
int koffset = 0;
// a list of colors
List<Color> colors = new List<Color>();

public void paintKs()
{
    if (Ks == null) return;

    Size s1 = pb_image.ClientSize;
    pb_image.Image = new Bitmap(s1.Width, s1.Height);
    Bitmap bmp = new Bitmap(pb_image.Image);

    PixelFormat fmt1 = bmp.PixelFormat;
    byte bpp1 = 4;

    Rectangle rect = new Rectangle(Point.Empty, s1);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, fmt1);

    int size1 = bmpData.Stride * bmpData.Height;
    byte[] data = new byte[size1];
    System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, data, 0, size1);

    for (int y = 0; y < s1.Height; y++)
    {
        for (int x = 0; x < s1.Width; x++)
        {
            int index = y * bmpData.Stride + x * bpp1;
            Color c = colors[(Ks[x, y] + koffset) % (colors.Count)];
            if (Ks[x, y] == 0) c = Color.Black;
            data[index + 0] = c.B;
            data[index + 1] = c.G;
            data[index + 2] = c.R;
            data[index + 3] = 255;
        }
    }

    System.Runtime.InteropServices.Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
    bmp.UnlockBits(bmpData);

    pb_image.Image = bmp;
}

void saveKs(string dataFile)
{
   using (BinaryWriter writer = new BinaryWriter(File.Open(dataFile, FileMode.Create)))
   {
        for (int y = 0; y < Ks.GetLength(0); y++)
            for (int x = 0; x < Ks.GetLength(1); x++)
                writer.Write((Int16)Ks[x, y]);
   }
}

void loadKs(string dataFile)
{
   int w = pb_image.ClientSize.Width;
   if (Ks == null) Ks = new int[w, w];

   using (BinaryReader reader = new BinaryReader(File.Open(dataFile, FileMode.Open)))
   {
        for (int y = 0; y < Ks.GetLength(0); y++)
            for (int x = 0; x < Ks.GetLength(1); x++)
                 Ks[x, y] = reader.ReadInt16();
   }

}

private void Test_Click(object sender, EventArgs e)
{
    loadKs("fractalData021.dat");
    for (int i = 0; i < 256; i++)
    {
        // a very simple and rather awful palette!
        for (int i = 0; i < 256; i++)  
             colors.Add(Color.FromArgb(255, i, i, 255 - i));
        for (int i = 0; i < 100; i++) 
             colors.Add(Color.FromArgb(255, i + 100, 255 -i, 155 - i));
        for (int i = 0; i < 100; i++) 
             colors.Add(Color.FromArgb(255, i + i+ 50, 255 - i - i, 155 - i/2));
    }
    paintKs();
    timer1.Intervall = 33;  // 30 fps
    timer1.Start();
}

private void timer1_Tick(object sender, EventArgs e)
{
    koffset++;
    if (koffset >= colors.Count) koffset = 0;;
    paintKs();
}

Here are a few files with test data; the test file has a size of 500x500 pixels:

http://www.file-upload.net/download-9796723/fractalData021.dat.html

http://www.file-upload.net/download-9796722/fractalData021.jpg.html

http://www.file-upload.net/download-9796721/fractalData021.txt.html

Update:

Here is code to do a plaette rotation on a non-dithered GIF file.

void palRotate()
{
    Bitmap bmp = (Bitmap)pb_image.Image;
    var pal = bmp.Palette;
    for (int i = 0; i < 256; i++)  pal.Entries[(i + koffset) % 256] = colors[i];
    bmp.Palette = pal;
    pb_image.Image = bmp;
}

For preparation these calls would extract the original palette colors into the colors list:

pb_image.Image = new Bitmap("d:\\fff.gif");
Bitmap bmp = (Bitmap)pb_image.Image;
var pal = bmp.Palette;
for (int i = 0; i < 256; i++) colors.Add(pal.Entries[i]);

For this to look anything but totally crappy the pallete would have to have some sort of order; but even then the pixelated image will look pathetic..

It would be called in a similar way as the other rotation code from a Timer which advances the koffset variable.

TaW
  • 53,122
  • 8
  • 69
  • 111
0

This creates a palette that rotates the colours smoothly. It happens that 3 * 85 = 255 leaving 1 palette entry for the Mset pixels. I haven't coded it to suit your platform (and it would be better coded as a struct array), but you can see that I fade 2 primaries at a time. This avoids muddy colours.

#include <stdio.h>
#include <memory.h>

#define MAXITER 1000   // iteration depth

unsigned char pal [256][3];

int Mandelplot (int x, int y) {
    // return (palette) colour for pixel
    int c, i=iterate (x, y);      // your iteration function
    if (i >= MAXITER)
        c = 255;
     else
        c = i % 255;
    return c;
}

void rotate_palette (void) {
    // leaves index 255 unchanged for Mset pixels
    unsigned char r=pal[0][0], g=pal[0][1], b=pal[0][2];
    memcpy (&pal[0][0], &pal[1][0], 254 * 3 * sizeof(unsigned char));
    pal[254][0] = r;
    pal[254][1] = g;
    pal[254][2] = b;
}

int main (void) {
    unsigned char r=0, g=0, b=255;
    int i;

    for (i=0; i<85; i++) {
        pal [i][0]= r;
        pal [i][1]= g;
        pal [i][2]= b;
        b -= 3;
        g += 3;
    }

    for (i=85; i<170; i++) {
        pal [i][0]= r;
        pal [i][1]= g;
        pal [i][2]= b;
        g -= 3;
        r += 3;
    }

    for (i=170; i<255; i++) {
        pal [i][0]= r;
        pal [i][1]= g;
        pal [i][2]= b;
        r -= 3;
        b += 3;
    }

    pal [255][0] = 0;  // black on the Mset
    pal [255][1] = 0;
    pal [255][2] = 0;

    for (i=0; i<256; i++)
       printf ("%02X %02X %02X, ", pal[i][0], pal[i][1], pal[i][2]);

    return 0;
}
Weather Vane
  • 33,872
  • 7
  • 36
  • 56