Using Core Graphics to change an images colorspace profile using AppleScriptObjC - core-graphics

I already have some code that will do most of what I need using NSIMage and NSColorSpace. Unfortunatly I am trying to recreate a colorspace/profile change that happens in Photoshop, and it is a bit more complex than what NSColorSpace can do. You can see that post here:
Using ApplescriptObjC to convert color spaces of an image using NSColorSpace and iccProfileData
So what I need help with is either adding in the following from CGColorSpace or recreating certain parts of the script so they work from the start with Core Graphics. The functions that I am looking to accomplish are:
CGColorRenderingIntent using kCGRenderingIntentPerceptual
kCGColorConversionBlackPointCompensation
Plus using dithering as a part of this color space conversion, but I can't seem to find an option for that in the Apple Objective-C documentation.
NSColor does have NSColorRenderingIntentPerceptual but it does not seem like there is the BlackPointCompensation under NSColor.
I think I have identified all the parts I need to build this script. I think the script is partway written already. I just need some help gluing the last few bits together.
I believe the script will still need to open the profile into NSData (The file is POSIX file reference to the ICC Profile that I am using)
set theData to current application's NSData's dataWithContentsOfFile:theFile
Now I need to open the image, my hope that this is the same whether using NSColor or CGColor:
set theInput to (choose file with prompt "Choose RGB file")
set theOutput to (choose file name default name "Untitled.jpg")
set theImage to current application's NSImage's alloc()'s initWithContentsOfURL:theInput
set imageRep to theImage's representations()'s objectAtIndex:0
Here is what I see the line of code that I need the most help with. This is actually where the color conversion is happening with NSColorSpace:
set targetSpace to current application's NSColorSpace's alloc's initWithICCProfileData:theData
It seems like I should be using CGColorSpaceCreateICCBased with CGDataProviderRef and then theFile, but I doubt that I can just put those in place of the NSColorSpace and initWithICCProfileData. I also need to graft onto this line, or a new line, the CGColorRenderingIntent using kCGRenderingIntentPerceptual and kCGColorConversionBlackPointCompensation (With dither if that option even exists).
I am not sure if the next two lines need to be updated, but I am pretty sure that the third line can stay the same (or I am really stupid, forgive me).
set theProps to current application's NSDictionary's dictionaryWithObjects:{1.0, true} forKeys:{current application's NSImageCompressionFactor, current application's NSImageProgressive}
set jpegData to bitmapRep's representationUsingType:(current application's NSJPEGFileType) |properties|:theProps
jpegData's writeToURL:theOutput atomically:true
So the input would be an RGB with an generic sRGB profile file and the output would be a CMYK file with a specific CMYK Profile (GRACoL2013_CRPC6.icc to be exact).

The input would be an RGB with an generic sRGB profile file and the output would be a CMYK file with a specific CMYK Profile (GRACoL2013_CRPC6.icc)
If this accurately summarises the objective, you ought to be able to do this using Image Events, which is an AppleScriptable faceless program to manipulate images.
Played around with Image Events, but embedding a new colour profile—which ought to be possible—doesn't appear to take, and the original colour profile remains.
So I wrote the AppleScriptObjC equivalent:
use framework "Foundation"
use framework "AppKit"
use scripting additions
property this : a reference to the current application
property nil : a reference to missing value
property _1 : a reference to reference
property NSBitmapImageRep : a reference to NSBitmapImageRep of this
property NSColorSpace : a reference to NSColorSpace of this
property NSData : a reference to NSData of this
property NSImage : a reference to NSImage of this
property NSString : a reference to NSString of this
property NSURL : a reference to NSURL of this
property JPEG : a reference to 3
property PNG : a reference to 4
property NSFileType : {nil, nil, "jpg", "png"}
property options : {NSImageCompressionFactor:0.75, NSImageProgressive:true ¬
, NSImageColorSyncProfileData:a reference to iccData}
property NSColorRenderingIntent : {Default:0, AbsoluteColorimetric:1 ¬
, RelativeColorimetric:2, Perceptual:3, Saturation:4}
--------------------------------------------------------------------------------
# IMPLEMENTATION:
set iccProfile to loadICCProfile("~/Path/To/GRACoL2013_CRPC6.icc")
set image to _NSImage("~/Path/To/SourceImage.jpg")
set path of image to "~/Path/To/OutputImage.jpg" -- omit to overwrite source
set iccData to iccProfile's space's ICCProfileData()
my (write image for iccProfile given properties:contents of options)
--------------------------------------------------------------------------------
# HANDLERS & SCRIPT OBJECTS:
# __NSURL__()
# Takes a posix +filepath and returns an NSURL object reference
to __NSURL__(filepath)
local filepath
try
NSURL's fileURLWithPath:((NSString's ¬
stringWithString:filepath)'s ¬
stringByStandardizingPath())
on error
missing value
end try
end __NSURL__
# new()
# Instantiates a new NSObject
on new(_nsObject)
local _nsObject
_nsObject's alloc()
end new
# _NSImage()
# Creates a new NSImage instance with image data loaded from the +filepath
on _NSImage(filepath)
local filepath
script
property file : __NSURL__(filepath)
property data : new(NSImage)
property kind : JPEG
property path : nil -- write path (nil = overwrite source)
property size : nil
property name extension : NSFileType's item kind
to init()
my (data's initWithContentsOfURL:(my file))
end init
to lock()
tell my data to lockFocus()
end lock
to unlock()
tell my data to unlockFocus()
end unlock
end script
tell the result
init()
set its size to its data's |size|() as list
return it
end tell
end _NSImage
# ICCProfile()
# Loads a ColorSync profile from the +filepath and creates a new NSColorSpace
# instance
to loadICCProfile(fp)
local fp
script
property file : __NSURL__(fp)
property data : NSData's dataWithContentsOfURL:(my file)
property space : new(NSColorSpace)
property mode : NSColorRenderingIntent's Perceptual
to init()
(my space)'s initWithICCProfileData:(my data)
end init
end script
tell the result
init()
return it
end tell
end loadICCProfile
# write
# Writes out the +_NSImage data optionally converting it to a new colorspace
to write _NSImage for ICC : missing value ¬
given properties:(opt as record) : missing value
local _NSImage, ICC, kind, path, options
set ImageRep to new(NSBitmapImageRep)
_NSImage's lock()
ImageRep's initWithFocusedViewRect:{{0, 0}, _NSImage's size}
ImageRep's bitmapImageRepByConvertingToColorSpace:(ICC's space) ¬
renderingIntent:(ICC's mode)
result's representationUsingType:(_NSImage's kind) |properties|:opt
set ImageRep to the result
_NSImage's unlock()
set fURL to __NSURL__(_NSImage's path)
if fURL = missing value then set fURL to NSImage's file
set ext to _NSImage's name extension
if fURL's pathExtension() as text ≠ ext then ¬
fURL's URLByDeletingPathExtension()'s ¬
URLByAppendingPathExtension:ext
ImageRep's writeToURL:fURL atomically:yes
if the result = true then return fURL's |path|() as text
false
end write
---------------------------------------------------------------------------❮END❯
As you noted, there doesn't appear to be an equivalent Foundation class option for the Core Graphics' kCGColorConversionBlackPointCompensation when converting colour spaces. So I may not have provided you with anything script-wise that you weren't already able to do. What I did observe, however, is that the GRACoL colour profiles cause the AppleScript engine to crash if one tries to utilise them "as is" after obtaining them from the website. For whatever reason, the profile must first be opened in the ColorSync Utility.app, and then saved (Save As...) or exported (Export...). Overwriting the original file is fine, after which AppleScript appears content to use it. This doesn't appear to be an issue with other profiles already saved on the system.

Related

Confused by CaretOffset/LanguageItem methods

I am trying to find out over which source file element the cursor is located (code is inside a pad)
//Obtain document
Document sf = IdeApp.Workbench.ActiveDocument;
//out argument
DocumentRegion dr;
//Call using offset
Microsoft.CodeAnalysis.ISymbol o = sf.GetLanguageItem(sf.Editor.CaretOffset , out dr);
The ISymbol returned "o" is Object's Equals. The document sf is a simple class with a parameterless constructor. The cursor is inside the constructor. I was expecting my class constructor.
Where is the error?
Ok. I found a work around to get context data out of the current editor caret offset. It requires to obtain AnalysisDocument from the current document, then the SemanticModel of the document and after obtaining this model, calling GetEnclosingSymbol with the caret offset.

pdfbox - add visual signature. COSObject cast error

In org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions have setVisualSignature method. I can create a visual signature from some other pdf stream that has a visual signature appearance (to copy the appearance).
1) I created a signature appearance pdf, and using the setVisualSignature() method, I manage to copy the visual signature. Everything works;
2) I change the visual signature (change image) from PDFBox. To get COSObject:
Iterator<Entry<COSObjectKey, Long>> xrefEntriesIt = doc.getDocument()
.getXrefTable().entrySet().iterator();
while (xrefEntriesIt.hasNext()) {
COSObject object = doc.getDocument().getObjectFromPool(
xrefEntriesIt.next().getKey());
if (object.getDictionaryObject(COSName.SUBTYPE) == COSName.IMAGE) {
changeImage(object, doc);
}
}
and to change Image:
private static void changeImage(COSObject obj, PDDocument doc) {
PDXObjectImage imageInPdf =
(PDXObjectImage) PDXObject.createXObject((COSStream) obj.getObject());
File inputFile = new File("/new_SIGNATURE_IMG.jpg");
PDXObjectImage newImage = new PDJpeg(doc, new FileInputStream(inputFile));
imageInPdf.getCOSStream().replaceWithStream(newImage.getCOSStream());
doc.save("/new.pdf");
}
Everything works.
3) When I call setVisualSignature() method with the new pdf and with the new appearance image (that I change with my code), I have that error:
Exception in thread "main" java.lang.ClassCastException:
org.apache.pdfbox.cos.COSObject cannot be cast to
org.apache.pdfbox.cos.COSDictionary at
org.apache.pdfbox.pdmodel.PDDocument.addSignature(PDDocument.java:474)
Thats samples
What happens? Do I change images incorrectly?
The difference between template.pdf and CHANGED_TEMPLATE.pdf is that the signature field dictionary in the former one contains its appearance streams dictionary as a direct object:
9 0 obj
<< [...] /AP<</N 8 0 R>>>>
endobj
while in the latter one the appearance streams dictionary is an indirect object only referenced from the signature field dictionary:
5 0 obj
<<
[...]
/AP 10 0 R
>>
[...]
10 0 obj
<<
/N 15 0 R
>>
This is perfectly ok, the PDF specification does not require it to be direct in general:
AP dictionary (Optional; PDF 1.2) An appearance dictionary specifying how the annotation shall be presented visually on the page (see 12.5.5, “Appearance Streams”). Individual annotation handlers may ignore this entry and provide their own appearances.
(Table 164 in ISO 32000-1:2008)
Unfortunately the code where the exception occurs, i.e. the PDDocument method addSignature` in line 474, looks like this:
PDAppearanceDictionary ap =
new PDAppearanceDictionary((COSDictionary)cosBaseDict.getItem(COSName.AP));
Thus, PDFBox here expects the /AP value to be a direct dictionary object, not some reference to an indirect dictionary object.
I assume your first manipulation makes PDFBox rewrite the PDF in a way it assumes to be best (which seems to include making dictionaries indirect objects), and then PDFBox has other expectations...
If you made your first manipulation as an incremental update instead of a complete rewrite, PDFBox might leave the appearances dictionary untouched.

Access Denied when deleting image file previously used in DataTemplate in WinRT

I have image (PNG) files used by my GridView as part of its DataTemplate. If I try to delete a specific object row in my GridView, I would delete the related image file for that row as well. The images are different for every item on the list.
I'm using this code to delete the image file
StorageFile _file = await DataStore.GetFileAsync(filename);
await _file.DeleteAsync(StorageDeleteOption.Default);
The image file is rendered on the GridView under the GridView's DataTemplate.
So in each object model in my List, I have a public property there that returns an ImageSource for my DataTemplate.
I'm calling my delete procedure right after i deleted the object row from the List and after the GridView has been refreshed of the new List items.
Even though the List does not contain the object (consuming the image) anymore, the app throws the Access is Denied exception if i try to delete the file. While the app is running, if i try to delete that particular file manually (through file explorer), it won't allow me too.
I tried clearing all unused objects in my app, even setting the GridView's ItemSource to null and the List to null before I delete the image. Still the exception persist.
Thanks in advance.
One method you can try is to load the image into a memory steam, then create a BitmapImage object from that stream, you can then set the source of your Image control to that bitmap image.
Since you are not using the actual image file as the source of the image, you can easily delete it anytime :)
Though this is an old question, I have encountered the problem recently in a UWP app and actually managed to find a solution. But first some background information about the problem:
When you create a BitmapImage with a URI, the created URI object holds a reference to the file in your local storage and keeps it open, i.e. non-writable. This btw is only true when the Bitmap is just big enough to fit into the Image entirely, typically small or medium sized Bitmaps. If the Bitmap happens to be big enough, WinRT automatically uses a downsampled version when it is displayed in an Image. In this case the URI does NOT hold a reference to the original file.
Now to the actual solution:
Setting Image.Source to null doesn't do the trick here, as the URI is still alive (until the next GC cycle at least). What DID work for me, was casting the Source to the BitmapImage it originally was and settings the UriSource to null.
var bitmapImage = image.Source as BitmapImage;
if (bitmapImage != null)
bitmapImage.UriSource = null;
And yes, this IS stupid.
Mark already mentioned part of this but there is no need to make it that complicated. Simply, wherever you expect that you need to delete the bitmap while the system is holding it, use a converter like this:
public class SafeImageFileConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
string path = (string)value;
var stream = new MemoryStream(File.ReadAllBytes(path));
return ImageSource.FromStream(() => stream);
}
}
In practice, you might want to check whether the path exists or return an error.png or similar if it doesn't. Also, don't be tempted to use using with the stream, the system will need the stream so you shouldn't dispose it early.
The trick is to use a Uri object to load the image (instead of a string filename) and then to use the EXACT same Uri instance to delete the file (after removing the image from the UI, of course). Here is an example:
//Save the Uri as a member variable so you can get to it later
private Uri uri;
//Create the Uri
uri = new Uri(OriginalImageFilename, UriKind.Absolute);
//Load the image
BitmapImage bitmapImage = new BitmapImage(uri);
//This can also be done by binding a Image control's source property to the uri.
//Delete the image (remember to use the same Uri instance)
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(uri);
await file.DeleteAsync();
As a work-around, i just deleted the unused images during app launch so that no processes are using it. Thanks.

Out of memory exception while loading images

I am using the following piece of code to load images as thumbnails to a FlowLayoutPanel control. Unfortunately i get an OutOfMemory exception.
As you already guess the memory leak is found at line
Pedit.Image = System.Drawing.Image.FromStream(fs)
So how could i optimize the following code?
Private Sub LoadImagesCommon(ByVal FlowPanel As FlowLayoutPanel, ByVal fi As FileInfo)
Pedit = New DevExpress.XtraEditors.PictureEdit
Pedit.Width = txtIconsWidth.EditValue
Pedit.Height = Pedit.Width / (4 / 3)
Dim fs As System.IO.FileStream
fs = New System.IO.FileStream(fi.FullName, IO.FileMode.Open, IO.FileAccess.Read)
Pedit.Image = System.Drawing.Image.FromStream(fs)
fs.Close()
fs.Dispose()
Pedit.Properties.SizeMode = DevExpress.XtraEditors.Controls.PictureSizeMode.Zoom
If FlowPanel Is flowR Then
AddHandler Pedit.MouseClick, AddressOf Pedit_MouseClick
AddHandler Pedit.MouseEnter, AddressOf Pedit_MouseEnter
AddHandler Pedit.MouseLeave, AddressOf Pedit_MouseLeave
End If
FlowPanel.Controls.Add(Pedit)
End Sub
Update: The problem occurs while loading a number of images (3264x2448px at 300dpi - each image is about 3Mb's)
Documentation for Image.FromFile (which is related to your FromStream) says that it will throw OutOfMemoryException if the file is not a valid image format or if GDI+ doesn't support the pixel format. Is it possible you're trying to load an unsupported image type?
Also, documentation for Image.FromStream says that you have to keep the stream open for the lifetime of the image, so even if your code loaded the image you'd probably get an error because you're closing the file while the image is still active. See http://msdn.microsoft.com/en-us/library/93z9ee4x.aspx.
Couple of thoughts:
First off, as Jim has stated, when using Image.FromStream the stream should remain open for the lifetime of the Image as remarked on the MSDN page. As such, I would suggest to copy the contents of the file to a MemoryStream, and use the latter to create the Image instance. So you can release the file handle asap.
Secondly, the images you're using are rather big (uncompressed, as they would exist in memory, Width x Height x BytesPerPixel). Assuming the context you use them in might allow for them to be smaller, consider resizing them, and potentially caching the resized versions somewhere for later use.
Lastly, don't forget to Dispose the image and the Stream when they are no longer needed.
You can solve this in a few steps:
to get free from the File-dependency, you have to copy the images. By really drawing it to a new Bitmap, you can't just copy it.
since you want thumbnails, and your source-bitmaps are rather large, combine this with shrinking the images.
I had the same problem. Jim Mischel answer led me to discover loading an innocent .txt file was the culprit. Here's my method in case anyone is interested.
Here's my method:
/// <summary>
/// Loads every image from the folder specified as param.
/// </summary>
/// <param name="pDirectory">Path to the directory from which you want to load images.
/// NOTE: this method will throws exceptions if the argument causes
/// <code>Directory.GetFiles(path)</code> to throw an exception.</param>
/// <returns>An ImageList, if no files are found, it'll be empty (not null).</returns>
public static ImageList InitImageListFromDirectory(string pDirectory)
{
ImageList imageList = new ImageList();
foreach (string f in System.IO.Directory.GetFiles(pDirectory))
{
try
{
Image img = Image.FromFile(f);
imageList.Images.Add(img);
}
catch
{
// Out of Memory Exceptions are thrown in Image.FromFile if you pass in a non-image file.
}
}
return imageList;
}

Filetype check in VB.NET?

I have an image resized program and it works. The problem is when a user selects a non-image file in the file select dialog, it crashes. How can I check for image files?
UPDATE: 2022-04-05
Since it may not be feasible to validate the binary structure of every supported image, the fastest way to check if a file contains an image is to actually load it. If it loads successfully, then it is valid. If it doesn't then it is not.
The code below can be used to check if a file contains a valid image or not. This code is updated to prevent locking the file while the method is called. It also handles resource disposal after the tests (thanks for pointing out this issue user1931470).
Public Function IsValidImage(fileName As String) As Boolean
Dim img As Drawing.Image = Nothing
Dim isValid = False
Try
' Image.FromFile locks the file until the image is disposed.
' This might not be the wanted behaviour so it is preferable to
' open the file stream and read the image from it.
Using stream = New System.IO.FileStream(fileName, IO.FileMode.Open)
img = Drawing.Image.FromStream(stream)
isValid = True
End Using
Catch oome As OutOfMemoryException
' Image.FromStream throws an OutOfMemoryException
' if the file does not have a valid image format.
isValid = False
Finally
' clean up resources
If img IsNot Nothing Then img.Dispose()
End Try
Return isValid
End Function
ORIGINAL ANSWER
⚠️⚠️ WARNING ⚠️⚠️
This code has a bug that causes a high memory consumption when called several times in a program's lifetime.
DO NOT USE THIS CODE!!
Here's the VB.NET equivalent of 0xA3's answer since the OP insisted on a VB version.
Function IsValidImage(filename As String) As Boolean
Try
Dim img As System.Drawing.Image = System.Drawing.Image.FromFile(filename)
Catch generatedExceptionName As OutOfMemoryException
' Image.FromFile throws an OutOfMemoryException
' if the file does not have a valid image format or
' GDI+ does not support the pixel format of the file.
'
Return False
End Try
Return True
End Function
You use it as follows:
If IsValidImage("c:\path\to\your\file.ext") Then
'do something
'
Else
'do something else
'
End If
Edit:
I don't recommend you check file extensions. Anyone can save a different file (text document for instance) with a .jpg extension and trick you app into beleiving it is an image.
The best way is to load the image using the function above or to open the first few bytes and check for the JPEG signature.
You can find more information about JPEG files and their headers here:
http://www.fastgraph.com/help/jpeg_header_format.html
http://en.wikipedia.org/wiki/JPEG
A very primitive check is to simply try to load the image. If it is not valid an OutOfMemoryException will be thrown:
static bool IsImageValid(string filename)
{
try
{
System.Drawing.Image img = System.Drawing.Image.FromFile(filename);
}
catch (OutOfMemoryException)
{
// Image.FromFile throws an OutOfMemoryException
// if the file does not have a valid image format or
// GDI+ does not support the pixel format of the file.
//
return false;
}
return true;
}
If I understood your question correctly your application it going to load the image anyway. Therefore simply wrapping the load operation in a try/catch block does not mean any additional overhead. For the VB.NET solution of this approach check the answer by #Alex Essilfie.
The ones wondering why Image.FromFile is throwing an OOM on invalid files should read the answer of Hans Passant to the following question:
Is there a reason Image.FromFile throws an OutOfMemoryException for an invalid image format?
Your first line of defense, of course, would be simply to check the file's extension:
Function IsImageFile(ByVal filename As String) As Boolean
Dim ext As String = Path.GetExtension(filename).ToLowerInvariant()
' This supposes your program can deal only with JPG files; '
' you could add other extensions here as necessary. '
Return ext = ".jpg" OrElse ext = ".jpeg"
End Function
Better yet, as SLC suggests in a comment, set your dialog's Filter property:
dialog.Filter = "Image files|*.jpg;*.jpeg"
This isn't a guarantee -- ideally you'd want to check the file itself to verify it's an image, and theoretically you should also be able to load files with anomalous extensions if they are in fact image files (maybe just ask for the user's acknowledgement first) -- but it's an easy start.
The VB and C# answers are great but also contain a "gotcha" if you plan to alter or move the file: the created 'img' object will lock the image file unless the dispose() method is invoked to release it. See below:
VB
Function IsValidImage(filename As String) As Boolean
Try
Dim img As System.Drawing.Image = System.Drawing.Image.FromFile(filename)
img.dispose() ' Removes file-lock of IIS
Catch generatedExceptionName As OutOfMemoryException
' Image.FromFile throws an OutOfMemoryException
' if the file does not have a valid image format or
' GDI+ does not support the pixel format of the file.
'
Return False
End Try
Return True
End Function
C#
static bool IsImageValid(string filename)
{
try
{
System.Drawing.Image img = System.Drawing.Image.FromFile(filename);
img.dispose(); // Removes file-lock of IIS
}
catch (OutOfMemoryException)
{
// Image.FromFile throws an OutOfMemoryException
// if the file does not have a valid image format or
// GDI+ does not support the pixel format of the file.
//
return false;
}
return true;
}
The most robust way would be to understand the signatures of the files you need to load.
JPEG has a particular header format, for example.
This way your code won't be as easily fooled if you just look at the extension.
163's answer should get you most of the way along these lines.