I've written a script to change all images in many hundreds of Excel documents to a .png format in order to reduce their file size (the largest were coming up on 25MB).
If the images in the Excel document are not in their original orientation i.e., if they have been rotated since being inserted in the document, then the new .png images, when added to the document, are distorted. They appear too flat and wide.
The process by which the images are changed is:
- Iterating through all workbooks in the directory
- Iterating through each sheet in the workbook
- Iterating through all shapes in the worksheet (Worksheet.Shapes)
- Extracting the shape size and location with Shape.Width, .Height, .Top, and .Left
- Copying the shape to the Clipboard and storing into a variable of type Bitmap
- Making Color.White transparent
- Saving as a .png
- Deleting the old shape (Shape.Delete)
- Inserting the new image (Shapes.AddPicture)
- Saving and closing the document
What I believe to be the relevant code is as shown below:
' Iterate over each sheet in the current workbook
For Each xlsSheet In xlsBook.Worksheets
xlsSheet.Unprotect() ' Unprotect the worksheet for editing
' Iterate over each Shape in the current worksheet
For Each xlsShape As Excel.Shape In xlsSheet.Shapes
copied = False
While Not copied
Try
If Not xlsShape.Name.Contains("Drop Down") Then ' Ignore images with name containing Drop Down (not sure what this is)
' Store Shape properties
shapeLeft = xlsShape.Left
shapeTop = xlsShape.Top
shapeWidth = xlsShape.Width
shapeHeight = xlsShape.Height
xlsShape.Copy() ' Copy the shape to the Clipboard
If Clipboard.ContainsImage Then
img = Clipboard.GetImage ' Store the image in a variable
img.MakeTransparent(Color.White) ' Use White as the transparent colour
imgName = temporaryImageLocation & xlsShape.Name & ".png"
img.Save(imgName, ImageFormat.Png) ' Save the image in a temporary location (as a .png file)
xlsShape.Delete() ' Delete the current Shape and replace it with the new .png file
xlsSheet.Shapes.AddPicture(imgName, MsoTriState.msoFalse, MsoTriState.msoCTrue, shapeLeft, shapeTop, shapeWidth, shapeHeight)
File.Delete(imgName) ' Delete the .png file in the temporary location
End If
End If
copied = True
Catch ex As System.Runtime.InteropServices.COMException
MsgBox(ex.Message)
End Try
End While
Next
Next
I've thought that perhaps the code is extracting the original height and width (which will have the inverse aspect ratio of the rotated image), but this isn't the case (I've checked other documents where the images appear just fine, despite having different original sizes). I've also extracted the images manually, saved as a .png, and reinserted (so that the images are now 'not rotated') before running the code and the images then appear to be just fine.
After checking maybe a dozen or so Excel documents that have been 'compressed' as expected, I'm pretty sure this has something to do with the image rotation in the original Excel document. I don't know how to work around this so that, in future, if the code comes across a rotated image, it doesn't falter.
I've tried extracting the Shape.Rotation value and rotating the shape with Shape.IncrementRotation(-Shape.Rotation)
. The issue with this is that the original shape is deleted and a new one added. I've tried creating an ArrayList to keep track of the shape names and the corresponding rotation on the original shape, but the shape names are changed upon deletion of the original shape and addition of the new shape. I'm not sure what Excel's naming convention for this is. I've also tried something similar by keeping track of the Shape.Left and Shape.Top values, but this doesn't seem to work either.
In other words, I'm finding it difficult to keep track of which rotation value is associated with which picture in the Excel document.