locked
Trouble with multipage TIFF using powershell - SaveAdd “A Generic error occurred in GDI+.” RRS feed

  • Question

  • I can't seem to get powershell to utilize the SaveAdd method for a multipage tiff. This is part of a larger script that I turned into a test bed to work my way through the Tiff image manipulation. So all I am trying to do here is convert a multipage tiff, into a new multipage tiff with a bit depth of 1 and a resolution of 200p (some baggage from the original script still present).

    The only issue I am having is with the SaveAdd() method. I am sure there is something simple I have overlooked. With very few tiff resources for powershell to peruse, most of my sources are C# so there could be something I am missing in platform difference. I attempted using streams, as used here. I have also referenced MSDN's Example, Bob Powell's example, and every search result that seemed related. I have been able to get just about every approach to work for the first frame (page) but I always get the same error on additional pages. Many thanks in advance.

    Here is the Code (Save it in a folder with a multipage tif and execute):
    Function Test-Image{
    [cmdletbinding()]
    param(
    
    )
    #Image Resolution in DPI
    $ImageRes = 200
    $ImageQuality = 100 # %
    $ImageBitDepth = 1 # ColorDepth
    $FileObjects = Get-ChildItem -File -Recurse -Exclude "Test-Image.ps1"
    $ImageFiles = ($FileObjects | Where-Object {".bmp",".gif",".jpg",".jpeg",".png",".tif",".tiff",".wmf" -eq $_.Extension -and !$_.PSIsContainer -and $_.fullname -notlike "$DropBoxPath\UnsupportedFiles\*"}).fullname
    
    #Process Image Files
    $imageCodec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() |Where-Object {$_.MimeType -eq "image/tiff"}
    $encoderParams = New-Object System.Drawing.Imaging.EncoderParameters(3)
    $encoderParams.Param[1] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::Quality, $ImageQuality)
    $encoderParams.Param[2] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::ColorDepth, $ImageBitDepth)
    
    foreach ($ImageFile in $ImageFiles) {
    
        #Load the Image
        $SourceImage = New-Object System.Drawing.Bitmap($ImageFile) -EV e
        If ($e.Count -eq 0) {
            #Generate Random File Name
            $NewFileName = "$([System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetRandomFileName())).tif"    
            $FrameCount = $SourceImage.GetFrameCount($SourceImage.FrameDimensionslist[0])
            # Make sure we start on the First Frame (Page)
            $SourceImage.SelectActiveFrame($SourceImage.FrameDimensionsList[0],0) | Out-Null
            #Create the New Tiff and load the first Frame.
            $NewImage = New-Object System.Drawing.Bitmap($SourceImage)
            $NewImage.setResolution($ImageRes,$ImageRes)
    
        for ($FrameIndex=0; $FrameIndex -lt $FrameCount; $FrameIndex++) {
                write-host "Processing $ImageFile Page $($FrameIndex + 1) of $FrameCount" #| Out-File -Append -FilePath $LogFile
                If ($FrameIndex -eq 0) {
                    #First Page
                    # Set Save Flag to MultiFrame
                    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag,"MultiFrame")
                    # Save the First Frame (Page)
                    $NewImage.Save("$([System.IO.Path]::GetDirectoryName($ImageFile))\$NewFileName",$imageCodec,$encoderParams)
                } Else {
                    #Additional Pages
                    # Set the Current Frame
                    $SourceImage.SelectActiveFrame($SourceImage.FrameDimensionsList[0],$FrameIndex) | Out-Null
                    # Switch Save Flag to add Frame
                    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag,"FrameDimensionPage")
                    $NewImage.SaveAdd($SourceImage,$encoderParams)
                }
            }
            # Cleanup
            $SourceImage.Dispose()
            $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag,"Flush")
            $NewImage.SaveAdd($encoderParams)
            $NewImage.Dispose()
            #Remove-Item -Path $ImageFile -Force
        } Else { 
            write-host "$ImageFile Could not be opened." | Out-File -Append -FilePath $LogFile  
        }
    }
    #Clear Variable
    Remove-Variable ImageFiles
    }
    Test-Image

    Here are the Errors, which I'm assuming are caused by the same problem:
    Exception calling "SaveAdd" with "2" argument(s): "A generic error occurred in GDI+."
    At V:\jshaw\My Pictures\test\Test-Image.ps1:48 char:6
    +                     $NewImage.SaveAdd($SourceImage,$encoderParams)
    +                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
        + FullyQualifiedErrorId : ExternalException
    
    Exception calling "SaveAdd" with "1" argument(s): "A generic error occurred in GDI+."
    At V:\jshaw\My Pictures\test\Test-Image.ps1:54 char:4
    +             $NewImage.SaveAdd($encoderParams)
    +             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
        + FullyQualifiedErrorId : ExternalException


    • Moved by Bill_Stewart Friday, December 12, 2014 3:24 PM Move to more appropriate forum
    Wednesday, December 10, 2014 3:06 PM

Answers

  • So I found the problem.  Apparently, the EncoderParameter(Encoder, String) constructor does not work as intended. Using the EncoderParameter(Encoder, Int) constructor works fine.

    So the following lines of code in my original script:

    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag,"MultiFrame")
    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag,"FrameDimensionPage")
    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag,"Flush")

    Should be changed to match these to make the script work:

    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag, [int][System.Drawing.Imaging.EncoderValue]::MultiFrame)
    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag, [int][System.Drawing.Imaging.EncoderValue]::FrameDimensionPage)
    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag, [int][System.Drawing.Imaging.EncoderValue]::Flush)


    Joshua Shaw

    • Marked as answer by jshaw01 Friday, December 12, 2014 4:40 PM
    Friday, December 12, 2014 4:40 PM

All replies

  • These may be helpful, you could be disposing one of the streams to early. C# is not an area of mine by any means but you can try holding off on disposing anything until the end of the script.

    Link 1

    Link 2

    Wednesday, December 10, 2014 3:15 PM
  • I tried move the Dispose statements further down with no joy. I had a look at both links.  In both of those cases the error was occurring with the Save() method.  My code is using the Save() method successfully for the first page, it is only failing when I try to add additional pages.

    I am beginning to wonder it is closing the file after the Save() rather than keeping it open for the SaveAdd().


    Joshua Shaw

    Thursday, December 11, 2014 9:15 PM
  • I'm at a loss.  To eliminate the powershell environment I attempted to run C# code from within my powershell script.  I am getting the same result.  This is my first attempt at C# so bear that in mind.  Maybe someone familiar with C# can tell me what I am doing wrong.  I basically altered the MSDN example to try to achieve my goals.

    Function Test-Image{
    	[cmdletbinding()]
    	param(
    		
    	)
    	#Image Resolution in DPI
    	$ImageRes = 200
    	$ImageQuality = 100 # %
    	$ImageBitDepth = 1 # ColorDepth
    	$FileObjects = Get-ChildItem -File -Recurse -Exclude "Test-Image.ps1"
    	$ImageFiles = ($FileObjects | Where-Object {".bmp",".gif",".jpg",".jpeg",".png",".tif",".tiff",".wmf" -eq $_.Extension -and !$_.PSIsContainer -and $_.fullname -notlike "$DropBoxPath\UnsupportedFiles\*"}).fullname
    	
    	$Assem = (
    		"System",
    		"System.Drawing",
    		"System.IO"
    	)
    	$Source = @"
    using System;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    
    
    namespace jshaw
    {
    	public class MultiFrame_Tiff
    	{
    		public static void Process_Tiff(string ImageFile)
    		{
    			Bitmap SourceImage;
    			Bitmap NewImage;
    			ImageCodecInfo myImageCodecInfo;
    			Encoder myEncoder;
    			EncoderParameter myEncoderParameter;
    			EncoderParameters myEncoderParameters;
    			
    			// Create three Bitmap objects.
    			SourceImage = new Bitmap(ImageFile);
    			SourceImage.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page,0);
    			NewImage = new Bitmap(SourceImage);
    			
    			// Create New File Name
    			string NewFileName = Path.GetFileNameWithoutExtension(Path.GetRandomFileName()) + ".tif";
    			
    			// Get an ImageCodecInfo object that represents the TIFF codec.
    			myImageCodecInfo = GetEncoderInfo("image/tiff");
    
    			// Create an Encoder object based on the GUID 
    			// for the SaveFlag parameter category.
    			myEncoder = Encoder.SaveFlag;
    
    			// 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 first page (frame).
    			myEncoderParameter = new EncoderParameter(myEncoder,(long)EncoderValue.MultiFrame);
    			myEncoderParameters.Param[0] = myEncoderParameter;
    			NewImage.Save(NewFileName, myImageCodecInfo, myEncoderParameters);
    			int FrameCount = SourceImage.GetFrameCount(System.Drawing.Imaging.FrameDimension.Page);
    			
    			for (int i = 1; i < FrameCount;i++)
    			{
    				SourceImage.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page,FrameCount);
    				// Save the second page (frame).
    				myEncoderParameter = new EncoderParameter(myEncoder,(long)EncoderValue.FrameDimensionPage);
    				myEncoderParameters.Param[0] = myEncoderParameter;
    				NewImage.SaveAdd(SourceImage, myEncoderParameters);
    			}
    
    			// Close the multiple-frame file.
    			myEncoderParameter = new EncoderParameter(myEncoder,(long)EncoderValue.Flush);
    			myEncoderParameters.Param[0] = myEncoderParameter;
    			NewImage.SaveAdd(myEncoderParameters);
    		}
    		private static ImageCodecInfo GetEncoderInfo(String mimeType)
    		{
    			int j;
    			ImageCodecInfo[] encoders;
    			encoders = ImageCodecInfo.GetImageEncoders();
    			for(j = 0; j < encoders.Length; ++j)
    			{
    				if(encoders[j].MimeType == mimeType)
    					return encoders[j];
    			}
    			return null;
    		}
    	}
    }
    

    "@
    Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source -Language CSharp
    	foreach ($ImageFile in $ImageFiles) {
        [jshaw.MultiFrame_Tiff]::Process_Tiff($ImageFile)
    		
    	}
    	#Clear Variable
    	Remove-Variable ImageFiles
    }
    Test-Image



    Joshua Shaw


    • Edited by jshaw01 Friday, December 12, 2014 2:20 PM
    Friday, December 12, 2014 2:17 PM
  • So I found the problem.  Apparently, the EncoderParameter(Encoder, String) constructor does not work as intended. Using the EncoderParameter(Encoder, Int) constructor works fine.

    So the following lines of code in my original script:

    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag,"MultiFrame")
    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag,"FrameDimensionPage")
    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag,"Flush")

    Should be changed to match these to make the script work:

    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag, [int][System.Drawing.Imaging.EncoderValue]::MultiFrame)
    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag, [int][System.Drawing.Imaging.EncoderValue]::FrameDimensionPage)
    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::SaveFlag, [int][System.Drawing.Imaging.EncoderValue]::Flush)


    Joshua Shaw

    • Marked as answer by jshaw01 Friday, December 12, 2014 4:40 PM
    Friday, December 12, 2014 4:40 PM
  • Glad you got it sorted!
    Friday, December 12, 2014 4:53 PM