CodeReaper
CodeReaper

Reputation: 6145

Saving multipage tiff without writing the whole file

I am facing a challenge where I need to do some work on the last page of a tiff file. So I wrote the example code below and it does work, it just works slowly. The code will eventually be executed quite a bit and I need to speed it up.

I have played with the idea of doing it multi-threaded, but I don't see that turning out well since in the end all the data must be written to the same file and that seems to the time consuming part.

I am hoping for an answer that can show me how to work solely on the last page, or something I haven't yet considered.

private void rotateLastPage() {
    string inputfile = "C:\\input.tif";
    string tmpfile = inputfile + ".tmp";

    Bitmap bmap = (Bitmap)Image.FromFile(inputfile);
    int max = bmap.GetFrameCount(FrameDimension.Page);

    Image[] images = new Image[max];
    int i;
    for (i = 0; i < max; i++)
    {
            bmap.SelectActiveFrame(FrameDimension.Page, i);
            images[i] = (Image)bmap.Clone();
    }
    bmap.Dispose();

    Bitmap pages = null;
    i = 0;
    foreach(Image image in images)
    {
        try
        {
            if (i == max - 1)
            {
                image.RotateFlip(RotateFlipType.Rotate90FlipNone);
            }
            EncoderParameters encoderParameters = new EncoderParameters(2);
            encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)EncoderValue.MultiFrame);

            Int16 c = BitConverter.ToInt16(image.PropertyItems[Array.IndexOf(image.PropertyIdList, 0x103)].Value, 0);

            if (c != 4 && c != 5)
            {
                throw new ArgumentException("Only CCIT4 and LZW compressed images are allowed.");
            }
            else if (c == 4)
            {
                encoderParameters.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, (long)EncoderValue.CompressionCCITT4);
            }
            else if (c == 5)
            {
                encoderParameters.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, (long)EncoderValue.CompressionLZW);
            }

            if (i == 0)
            {
                pages = (Bitmap)image;
                ImageCodecInfo encoder = getTiffEncoder();
                pages.Save(tmpfile, encoder, encoderParameters);
            }
            else
            {
                encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)EncoderValue.FrameDimensionPage);
                pages.SaveAdd(image, encoderParameters);
            }

            if (i == max - 1)
            {
                encoderParameters = new EncoderParameters(1);
                encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)EncoderValue.Flush);
                pages.SaveAdd(encoderParameters);
            }
            i++;
        }
        catch (Exception e)
        {
            Console.WriteLine(String.Format("Unable to rotate page {0} in file {1} due to {2}", (i + 1).ToString(), inputfile, e.Message));
            break;
        }
    }

    foreach (Image image in images) {
        image.Dispose();
    }
    pages.Dispose();

    // eventually overwrite input file with tmp file
}

private static ImageCodecInfo getTiffEncoder()
{
    ImageCodecInfo encoder = null;
    ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();
    for (int i = 0; i < encoders.Length; i++)
    {
        if (encoders[i].MimeType == "image/tiff")
        {
            encoder = encoders[i];
        }
    }
    if (encoder == null)
    {
        throw new NotSupportedException("Unable to find a tiff encoder.");
    }
    return encoder;
}

Upvotes: 0

Views: 3900

Answers (2)

knaki02
knaki02

Reputation: 183

Instead of working on a temporary file, you may work on the original file and save only the rotation on the last page. (EDIT : it seems to not work)


EDIT : Clone only the first frame, add all other frame and rotate the last..

Something like this :

public void rotateLastPage()
    {
        string inputfile = @"u:\\input.tiff";

        Bitmap bmap = (Bitmap)Image.FromFile(inputfile);
        int max = bmap.GetFrameCount(FrameDimension.Page);

        try
        {
            EncoderParameters encoderParameters = GetEncoderParameters(bmap, EncoderValue.MultiFrame);
            ImageCodecInfo encoder = GetTiffEncoder();
            var firstPage = (Image)bmap.Clone();
            firstPage.Save(inputfile+".tmp", encoder, encoderParameters);
            for (int i = 1; i < max - 1; i++)
            {
                bmap.SelectActiveFrame(FrameDimension.Page, i);
                encoderParameters = GetEncoderParameters(bmap, EncoderValue.FrameDimensionPage);
                firstPage.SaveAdd(bmap, encoderParameters);
            }
            bmap.SelectActiveFrame(FrameDimension.Page, max - 1);
            bmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
            encoderParameters = GetEncoderParameters(bmap, EncoderValue.FrameDimensionPage);
            firstPage.SaveAdd(bmap, encoderParameters);
            firstPage.SaveAdd(GetEncoderParameters(EncoderValue.Flush));
        }
        catch (Exception e)
        {
            Console.WriteLine(String.Format("Unable to rotate page {0} in file {1} due to {2}", max.ToString(), inputfile, e.Message));
        }
        bmap.Dispose();
    }

    private static EncoderParameters GetEncoderParameters(Image image, EncoderValue encoderValue)
    {
        EncoderParameters encoderParameters = new EncoderParameters(2);
        encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)encoderValue);
        encoderParameters.Param[1] = GetCompressionEncoder(image);
        return encoderParameters;
    }
    private static EncoderParameters GetEncoderParameters(EncoderValue encoderValue)
    {
        EncoderParameters encoderParameters = new EncoderParameters(1);
        encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)encoderValue);            
        return encoderParameters;
    }

    private static EncoderParameter GetCompressionEncoder(Image image)
    {
        Int16 c = BitConverter.ToInt16(image.PropertyItems[Array.IndexOf(image.PropertyIdList, 0x103)].Value, 0);

        if (c == 4)
        {
            return new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, (long)EncoderValue.CompressionCCITT4);
        }
        else if (c == 5)
        {
            return new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, (long)EncoderValue.CompressionLZW);
        }
        throw new ArgumentException("Only CCIT4 and LZW compressed images are allowed.");
    }

    private static ImageCodecInfo GetTiffEncoder()
    {
        ImageCodecInfo encoder = null;
        ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();
        for (int i = 0; i < encoders.Length; i++)
        {
            if (encoders[i].MimeType == "image/tiff")
            {
                encoder = encoders[i];
            }
        }
        if (encoder == null)
        {
            throw new NotSupportedException("Unable to find a tiff encoder.");
        }
        return encoder;
    }

Upvotes: 1

krassdanke
krassdanke

Reputation: 637

You are using a foreach-loop and a for-loop in one. If you would be using a List, the last page is accessed by images.Last(), and the first is accessed by images.First(), returning the corresponding image-objects.

Also right now you seem to be saving every single image for itself. I don't know how many pages you have, but if those are a bunch, that might take long. Have you tried processing them all and then at the end save them all at once? Because accessing the hard disk is time hungry. Had that problem when I processed certain lines of a textfile and save it into a new one once..... If that is not what you meant with your restriction of "without writing the whole file".

Hope that helps!

Upvotes: 1

Related Questions