3 Replies - 9711 Views - Last Post: 02 May 2011 - 12:49 PM Rate Topic: -----

#1 callahan09   User is offline

  • D.I.C Head

Reputation: 20
  • View blog
  • Posts: 61
  • Joined: 13-April 10

How do I resize a TIFF and keep all other image properties?

Posted 02 May 2011 - 11:54 AM

First, here's what I'm doing right now:

Bitmap myBitmap;
ImageCodecInfo myImageCodecInfo;
Encoder myEncoder;
EncoderParameter myEncoderParameter;
EncoderParameters myEncoderParameters;

string output_folder = @"OUTPUT\";
string file = "test.tif";

// Create a Bitmap object based on a BMP file.
myBitmap = new Bitmap(file);

myBitmap = new Bitmap(myBitmap, 1700, 2800);
myBitmap.SetResolution(200, 200);

// Get an ImageCodecInfo object that represents the TIFF codec.
myImageCodecInfo = GetEncoderInfo("image/tiff");

// Create an Encoder object based on the GUID
// for the Compression parameter category.
myEncoder = Encoder.Compression;

// Create an EncoderParameters object.
// An EncoderParameters object has an array of EncoderParameter
// objects. In this case, there is only one
// EncoderParameter object in the array.
myEncoderParameters = new EncoderParameters(1);

// Save the bitmap as a TIFF file with CCITT4 compression.
myEncoderParameter = new EncoderParameter(
    myEncoder,
    (long)EncoderValue.CompressionCCITT4);
myEncoderParameters.Param[0] = myEncoderParameter;

myBitmap.Save(output_folder + file, myImageCodecInfo, myEncoderParameters);


And at the myBitmap.Save(...) call, I get a "Parameter is not valid" error.

So I have also tried about a dozen other methods of doing this that are much, MUCH more complex and lead to exactly the same error.

So here's what I need to do:
1. Must resize dimensions (maximum width 1700 pixels, maximum height 2800 pixels, maintain original aspect ratio) [The calculations for this are simple, I know how to calculate the "new dimensions"... just don't know how to actually do the resizing].
2. Must be 200x200 dpi resolution.
3. Must be 1-bit color depth (black & white).
4. Must be encoded in TIFF CCITT4.

Please help all you can. I've already spent about 7 hours on this today and have been going in circles and can't get it to save the file with all of those requirements met.

Thanks.

Is This A Good Question/Topic? 0
  • +

Replies To: How do I resize a TIFF and keep all other image properties?

#2 tlhIn`toq   User is offline

  • Xamarin Cert. Dev.
  • member icon

Reputation: 6537
  • View blog
  • Posts: 14,450
  • Joined: 02-June 10

Re: How do I resize a TIFF and keep all other image properties?

Posted 02 May 2011 - 12:25 PM

Amongst other issues you don't seem to be doing anything to maintain the aspect ratio as stated in your homework instructions.

I notice that outputfolder doesn't seem to have any volume mentioned.
Its just @"Output\" not C:\\Output for example

I notice you assume the folder exists and don't check for it or try to make it if it doesn't

Let's start by putting a breakpoint at line 35, then taking a screenshot of the 'Locals' pallet showing the values of myImageCodecInfo & myEncoderParameters
Was This Post Helpful? 0
  • +
  • -

#3 callahan09   User is offline

  • D.I.C Head

Reputation: 20
  • View blog
  • Posts: 61
  • Joined: 13-April 10

Re: How do I resize a TIFF and keep all other image properties?

Posted 02 May 2011 - 12:37 PM

View PosttlhIn`toq, on 02 May 2011 - 02:25 PM, said:

Amongst other issues you don't seem to be doing anything to maintain the aspect ratio as stated in your homework instructions.

I notice that outputfolder doesn't seem to have any volume mentioned.
Its just @"Output\" not C:\\Output for example

I notice you assume the folder exists and don't check for it or try to make it if it doesn't

Let's start by putting a breakpoint at line 35, then taking a screenshot of the 'Locals' pallet showing the values of myImageCodecInfo & myEncoderParameters


Well, as far as the aspect ratio thing goes, I just left out unnecessary code. It wasn't relevant to my issue, so I just cleaned up the code so you only see what's necessary and then at the end I mentioned that I don't need help with that aspect so nobody would waste their time helping me with a problem I'd already solved. I'm sorry if that was confusing, though. Same goes for things like the output folder... I have checks going on to see if it exists and if not then to create it. Didn't deem that necessary for you to see either.

So, on to some discoveries I made: the reason it's giving me an invalid parameter exception is because when I convert to the new size (here:

myBitmap = new Bitmap(myBitmap, 1700, 2800);


) I am getting a 32bpp result. CCITT4 only works with 1bpp (or maybe it only worked with indexed PixelFormats?, either way it doesn't work with 32bpp). So that's where the exception is coming from.

I solved my problem by finding the following code online somewhere (darnit, I can't remember where, now) to convert PixelFormat to 1bpp:

        /// <summary>
        /// Copies a bitmap into a 1bpp/8bpp bitmap of the same dimensions, fast
        /// </summary>
        /// <param name="b">original bitmap</param>
        /// <param name="bpp">1 or 8, target bpp</param>
        /// <returns>a 1bpp copy of the bitmap</returns>
        static System.Drawing.Bitmap CopyToBpp(System.Drawing.Bitmap b, int bpp)
        {
            if (bpp != 1 && bpp != 8) throw new System.ArgumentException("1 or 8", "bpp");

            // Plan: built into Windows GDI is the ability to convert
            // bitmaps from one format to another. Most of the time, this
            // job is actually done by the graphics hardware accelerator card
            // and so is extremely fast. The rest of the time, the job is done by
            // very fast native code.
            // We will call into this GDI functionality from C#. Our plan:
            // (1) Convert our Bitmap into a GDI hbitmap (ie. copy unmanaged->managed)
            // (2) Create a GDI monochrome hbitmap
            // (3) Use GDI "BitBlt" function to copy from hbitmap into monochrome (as above)
            // (4) Convert the monochrone hbitmap into a Bitmap (ie. copy unmanaged->managed)

            int w = b.Width, h = b.Height;
            IntPtr hbm = b.GetHbitmap(); // this is step (1)
            //
            // Step (2): create the monochrome bitmap.
            // "BITMAPINFO" is an interop-struct which we define below.
            // In GDI terms, it's a BITMAPHEADERINFO followed by an array of two RGBQUADs
            BITMAPINFO bmi = new BITMAPINFO();
            bmi.biSize = 40;  // the size of the BITMAPHEADERINFO struct
            bmi.biWidth = w;
            bmi.biHeight = h;
            bmi.biPlanes = 1; // "planes" are confusing. We always use just 1. Read MSDN for more info.
            bmi.biBitCount = (short)bpp; // ie. 1bpp or 8bpp
            bmi.biCompression = BI_RGB; // ie. the pixels in our RGBQUAD table are stored as RGBs, not palette indexes
            bmi.biSizeImage = (uint)(((w + 7) & 0xFFFFFFF8) * h / 8);
            bmi.biXPelsPerMeter = 1000000; // not really important
            bmi.biYPelsPerMeter = 1000000; // not really important
            // Now for the colour table.
            uint ncols = (uint)1 << bpp; // 2 colours for 1bpp; 256 colours for 8bpp
            bmi.biClrUsed = ncols;
            bmi.biClrImportant = ncols;
            bmi.cols = new uint[256]; // The structure always has fixed size 256, even if we end up using fewer colours
            if (bpp == 1) { bmi.cols[0] = MAKERGB(0, 0, 0); bmi.cols[1] = MAKERGB(255, 255, 255); }
            else { for (int i = 0; i < ncols; i++) bmi.cols[i] = MAKERGB(i, i, i); }
            // For 8bpp we've created an palette with just greyscale colours.
            // You can set up any palette you want here. Here are some possibilities:
            // greyscale: for (int i=0; i<256; i++) bmi.cols[i]=MAKERGB(i,i,i);
            // rainbow: bmi.biClrUsed=216; bmi.biClrImportant=216; int[] colv=new int[6]{0,51,102,153,204,255};
            //          for (int i=0; i<216; i++) bmi.cols[i]=MAKERGB(colv[i/36],colv[(i/6)%6],colv[i%6]);
            // optimal: a difficult topic: http://en.wikipedia.org/wiki/Color_quantization
            // 
            // Now create the indexed bitmap "hbm0"
            IntPtr bits0; // not used for our purposes. It returns a pointer to the raw bits that make up the bitmap.
            IntPtr hbm0 = CreateDIBSection(IntPtr.Zero, ref bmi, DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0);
            //
            // Step (3): use GDI's BitBlt function to copy from original hbitmap into monocrhome bitmap
            // GDI programming is kind of confusing... nb. The GDI equivalent of "Graphics" is called a "DC".
            IntPtr sdc = GetDC(IntPtr.Zero);       // First we obtain the DC for the screen
            // Next, create a DC for the original hbitmap
            IntPtr hdc = CreateCompatibleDC(sdc); SelectObject(hdc, hbm);
            // and create a DC for the monochrome hbitmap
            IntPtr hdc0 = CreateCompatibleDC(sdc); SelectObject(hdc0, hbm0);
            // Now we can do the BitBlt:
            BitBlt(hdc0, 0, 0, w, h, hdc, 0, 0, SRCCOPY);
            // Step (4): convert this monochrome hbitmap back into a Bitmap:
            System.Drawing.Bitmap b0 = System.Drawing.Bitmap.FromHbitmap(hbm0);
            //
            // Finally some cleanup.
            DeleteDC(hdc);
            DeleteDC(hdc0);
            ReleaseDC(IntPtr.Zero, sdc);
            DeleteObject(hbm);
            DeleteObject(hbm0);
            //
            return b0;
        }

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        public static extern bool DeleteObject(IntPtr hObject);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        public static extern int InvalidateRect(IntPtr hwnd, IntPtr rect, int bErase);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        public static extern IntPtr GetDC(IntPtr hwnd);

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        public static extern IntPtr CreateCompatibleDC(IntPtr hdc);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        public static extern int DeleteDC(IntPtr hdc);

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        public static extern int BitBlt(IntPtr hdcDst, int xDst, int yDst, int w, int h, IntPtr hdcSrc, int xSrc, int ySrc, int rop);
        static int SRCCOPY = 0x00CC0020;

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFO bmi, uint Usage, out IntPtr bits, IntPtr hSection, uint dwOffset);
        static uint BI_RGB = 0;
        static uint DIB_RGB_COLORS = 0;
        [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
        public struct BITMAPINFO
        {
            public uint biSize;
            public int biWidth, biHeight;
            public short biPlanes, biBitCount;
            public uint biCompression, biSizeImage;
            public int biXPelsPerMeter, biYPelsPerMeter;
            public uint biClrUsed, biClrImportant;
            [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 256)]
            public uint[] cols;
        }

        static uint MAKERGB(int r, int g, int B)/>
        {
            return ((uint)(b & 255)) | ((uint)((r & 255) << 8)) | ((uint)((g & 255) << 16));
        }


        /// <summary>
        /// Copies a bitmap into a 1bpp bitmap of the same dimensions, slowly, using code from Bob Powell's GDI+ faq http://www.bobpowell.net/onebit.htm
        /// </summary>
        /// <param name="b">original bitmap</param>
        /// <returns>a 1bpp copy of the bitmap</returns>
        static System.Drawing.Bitmap FaqCopyTo1bpp(System.Drawing.Bitmap B)/>
        {
            int w = b.Width, h = b.Height; System.Drawing.Rectangle r = new System.Drawing.Rectangle(0, 0, w, h);
            if (b.PixelFormat != System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
            {
                System.Drawing.Bitmap temp = new System.Drawing.Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
                System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(temp);
                g.DrawImage(b, r, 0, 0, w, h, System.Drawing.GraphicsUnit.Pixel);
                g.Dispose(); b = temp;
            }
            System.Drawing.Imaging.BitmapData bdat = b.LockBits(r, System.Drawing.Imaging.ImageLockMode.ReadOnly, b.PixelFormat);
            System.Drawing.Bitmap b0 = new System.Drawing.Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format1bppIndexed);
            System.Drawing.Imaging.BitmapData b0dat = b0.LockBits(r, System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format1bppIndexed);
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    int index = y * bdat.Stride + (x * 4);
                    if (System.Drawing.Color.FromArgb(System.Runtime.InteropServices.Marshal.ReadByte(bdat.Scan0, index + 2), System.Runtime.InteropServices.Marshal.ReadByte(bdat.Scan0, index + 1), System.Runtime.InteropServices.Marshal.ReadByte(bdat.Scan0, index)).GetBrightness() > 0.5f)
                    {
                        int index0 = y * b0dat.Stride + (x >> 3);
                        byte p = System.Runtime.InteropServices.Marshal.ReadByte(b0dat.Scan0, index0);
                        byte mask = (byte)(0x80 >> (x & 0x7));
                        System.Runtime.InteropServices.Marshal.WriteByte(b0dat.Scan0, index0, (byte)(p | mask));
                    }
                }
            }
            b0.UnlockBits(b0dat);
            b.UnlockBits(bdat);
            return b0;
        }


So after creating the new Bitmap with the new size, I just run it through that CopyToBpp function with 1 for the bpp parameter and then when I go to save the result as with my CCITT4 encoding, it works flawlessly.

Thanks for your attention to my issue.

Edit: Just for the sake of summarizing my issue and my solution, when I create a new Bitmap in order to assign it a different height and width, it's output does not retain the original PixelFormat, it instead automatically outputs a 32bpp PixelFormat. The CCITT4 encoding is incompatible with this PixelFormat (and I need it to be in 1bpp PixelFormat anyway), so I found some code online to convert PixelFormat to 1bpp, run my resultant 32bpp Bitmap through this code to return the image back in 1bpp (now in my new size, as well), and it is then capable of being saved with CCITT4 encoding.

This post has been edited by callahan09: 02 May 2011 - 12:42 PM

Was This Post Helpful? 1
  • +
  • -

#4 tlhIn`toq   User is offline

  • Xamarin Cert. Dev.
  • member icon

Reputation: 6537
  • View blog
  • Posts: 14,450
  • Joined: 02-June 10

Re: How do I resize a TIFF and keep all other image properties?

Posted 02 May 2011 - 12:49 PM

Thanks for sharing your solution so others looking at this thread will benefit from your research!
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1