Reputation: 3035
in the Panel paint method, I want to create a TCIFilter the first time its called, then reuse the filter in subsequent calls. so this is the code. it just toggles a form level variable FFirst. So here's a bit of the code
if FFirst then
begin
...
filter := TCIFilter.Wrap(TCIFilter.OCClass.filterWithName(NSSTR('CISepiaTone')));
...
filter.retain;
FFirst := false;
end;
If I leave out the retain then subsequent calls to the paint method throw an exception when I try to use the filter(unrecognised selector sent to instance - because I'd guess filter is no longer a TCFilter).
But filter I've made a global variable, so it never goes out of scope, so why do I need the retain? why does the interface lose the reference? Something I'm missing, this is on OSX using XE6, but I'd assume the same applies on iOS, tia
Edit: here's all the code - maybe there's something else I'm doing wrong. drop a panel and a trackbar on a form and connect the panel.OnPaint and trackbar.OnChange events
unit Unit3;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
system.Rtti, FMX.Platform.Mac, FMX.Canvas.Mac, Macapi.CoreGraphics,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
Macapi.Helpers, MacApi.Foundation, Macapi.QuartzCore,
MacApi.CocoaTypes, Macapi.ObjectiveC;
type
TForm3 = class(TForm)
Button1: TButton;
Button2: TButton;
Panel1: TPanel;
TrackBar1: TTrackBar;
procedure Panel1Paint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
procedure FormCreate(Sender: TObject);
procedure TrackBar1Change(Sender: TObject);
private
cgImage : CGImageRef;
FFirst : Boolean;
{ Private declarations }
public
{ Public declarations }
end;
var
Form3: TForm3;
const
CoreImageFwk = '/System/Library/Frameworks/CoreImage.framework/CoreImage';
implementation
{$R *.fmx}
type
CIFilterClass = interface(NSObjectClass)
['{FB4A9CD9-7D60-482D-A0F4-4F78FC6E7E8D}']
{class} function filterNamesInCategories(categories: NSArray): NSArray; cdecl;
{class} function filterNamesInCategory(category: NSString): NSArray; cdecl;
{class} function filterWithImageData(data: NSData; options: NSDictionary): Pointer; cdecl;
{class} function filterWithImageURL(url: NSURL; options: NSDictionary): Pointer; cdecl;
{class} function filterWithName(name: NSString): Pointer; cdecl; overload;
{class} function filterWithName(name: NSString; keysAndValues: Pointer): Pointer; cdecl; overload;
{class} function localizedDescriptionForFilterName(filterName: NSString): NSString; cdecl;
{class} function localizedNameForCategory(category: NSString): NSString; cdecl;
{class} function localizedNameForFilterName(filterName: NSString): NSString; cdecl;
{class} function localizedReferenceDocumentationForFilterName(filterName: NSString): NSURL; cdecl;
end;
CIFilter = interface(NSObject)
['{2ACA27E7-D365-4AAC-A474-E72867CDE89A}']
function apply(apply: CIKernel): CIImage; cdecl; overload;
function apply(k: CIKernel; arguments: NSArray; options: NSDictionary): CIImage; cdecl; overload;
function attributes: NSDictionary; cdecl;
function inputKeys: NSArray; cdecl;
function isEnabled: Boolean; cdecl;
function outputKeys: NSArray; cdecl;
procedure setDefaults; cdecl;
// I added these 2 methods because they're not in FMX
procedure setValue(value: pointer; forKey : NSString); cdecl;
function outputImage : Pointer {CIImage}; cdecl;
end;
TCIFilter = class(TOCGenericImport<CIFilterClass, CIFilter>) end;
procedure TForm3.FormCreate(Sender: TObject);
begin
Ffirst := true;
end;
var
fileNameAndPath : Macapi.Foundation.NSURL;
beginImage, outputImage : CIImage;
context : CIContext;
filter : CIFilter;
pImage : Pointer;
p : CGPoint;
r : CGRect;
procedure TForm3.Panel1Paint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
var
d : double;
function GetCGContextFromCanvas(ACanvas: TCanvas): CGContextRef;
var
Context: TRttiContext;
Field: TRttiField;
begin
Field := Context.GetType(ACanvas.ClassType).GetField('FContext');
Assert(Field <> nil);
Result := PPointer(Field.GetValue(ACanvas).GetReferenceToRawData)^;
end;
function kCIContextOutputColorSpace: NSString;
begin
Result := CocoaNSStringConst(CoreImageFwk, 'kCIContextOutputColorSpace');
end;
begin
if FFirst then
begin
fileNameAndPath := TNSUrl.Wrap(TNSUrl.OCClass.fileURLWithPath(StrToNSStr('/path/to/an/image.png')));
beginImage := TCIImage.Wrap(TCIImage.OCClass.imageWithContentsOfURL(fileNameAndPath));
context := TCIContext.Wrap(TCIContext.OCClass.contextWithCGContext( GetCGContextFromCanvas(Canvas), nil ));
filter := TCIFilter.Wrap(TCIFilter.OCClass.filterWithName(NSSTR('CISepiaTone')));
filter.setValue( (beginImage as ILocalObject).GetObjectID, NSSTR('inputImage'));
filter.retain; // comment out this line and it crashes
context.retain; // same with this one
FFirst := false;
end;
d := TrackBar1.value/100;
filter.setValue( TNSNumber.OCClass.numberWithFloat(d), NSSTR('inputIntensity'));
outputImage := TCIImage.Wrap(filter.outputImage);
cgImage := context.createCGImage(outputImage, outputImage.extent);
p := CGPointMake(0,0);
r := CGRectMake(0,0, outputImage.extent.size.width, outputImage.extent.size.height);
CGContextDrawImage(GetCGContextFromCanvas(Canvas), r, cgImage);
CGImageRelease(cgImage);
end;
procedure TForm3.TrackBar1Change(Sender: TObject);
begin
Panel1.Repaint;
end;
end.
Edit 2: see here for the basic problem Delphi XE6 ARC on OSX releasing variables
Upvotes: 0
Views: 185
Reputation: 76724
You have to remember that Delphi uses ARC in your projects.
In order to do so it inserts code to increase and decrease the ref counts at appropriate points.
It inserts this code only for types that are subject to refcounting (interfaces, strings etc).
It does not add this code for other types; in particular pointers.
I strongly suspect your problem is here:
procedure setValue(value: pointer; forKey : NSString); cdecl;
You feed an object into setValue, but you masquerade it as a plain pointer.
This means the following will happen (I'm speculating here, because you did not include the code for SetValue
).
You copy the object passed in SetValue and assign it to Filter
. (This does not increase the refcount because you're using plain pointers).
The old Filter is replaced by value
and goes out of scope and is destroyed (this is as should be).
Filter is now a copy of the object passed in SetValue.
Meanwhile the original object goes out of scope; its refcount goes to zero and it gets destroyed.
Now Filter points to a destroyed reference.
The solution
Either ensure automatic refcounting occurs by making the value
parameter a refcounted type, or call value.retain
inside SetValue
.
Upvotes: 0