I have abstract iterator class:
TImageIterator = class (TObject)
protected
fCurLine, fCurPos: Integer;
public
//also basic constructors here, etc.
function ReadNextPixel: Cardinal; virtual; abstract;
function ReadNextAsGrayscale: Cardinal; virtual; abstract;
function ReadNextSubpixel: Cardinal; virtual; abstract;
end;
and lots of descendants, say, T1BitImageIterator
, T4BitImageIterator
, T8BitImageIterator
, T24BitRGBImageIterator
. The point is to create iterator (over non-rectangular area) suitable for current image/pixel format and then process image regardless of its type.
ReadNextPixel
returns 'raw pixel data': 0 or 1 in 1-bit image, 0..255 in 8-bit image or some sort of TColor in 24-bit RGB.
ReadNextAsGrayscale
returns luminance of pixel in range of 0..255, which in case of 8 bit image is just the same as ReadNextPixel
.
ReadNextSubpixel
works the same as ReadNextPixel
in grayscale/palette images, but returns R value, then (in next call) G value, then B value in 24 bit RGB image.
Right now I've got implementations like this:
function T8BitImageIterator.ReadNextPixel: Cardinal;
begin
Result:=fByteLine[fCurPos];
inch(fCurPos);
if fCurPos=fRight then begin
//going to next scanline, checking for end of iterated area etc
end;
end;
//another 'unique' functions
//code we'd be happy to get rid of
function T8BitImageIterator.ReadNextAsGrayscale: Cardinal;
begin
Result:=ReadNextPixel;
end;
function T1BitImageIterator.ReadNextSubpixel: Cardinal;
begin
Result:=ReadNextPixel;
end;
function T4BitImageIterator.ReadNextSubpixel: Cardinal;
begin
Result:=ReadNextPixel;
end;
function T8BitImageIterator.ReadNextSubpixel: Cardinal;
begin
Result:=ReadNextPixel;
end;
These functions should be as fast as possible, so extra function call seems ugly, but copy-pasting the whole functions is even worse!
What I'd like to do is something like this:
T8BitImageIterator = class (TImageIterator)
public
function ReadNextPixel: Cardinal; override;
function ReadNextAsGrayscale=ReadNextPixel; override;
...
end;
but there is no such syntax of course.
It can be done with interfaces, but they are much slower then virtual functions calls. Also, it's possible to define
TReadNextPixelProc = function: Cardinal of object;
TImageIterator = class(TObject)
...
public
ReadNextAsGrayscale: TReadNextPixelProc;
...
end;
and then initialize this variable in constructors, but this still have extra cost and makes these iterators more difficult to use (we must remember what is TReadNextPixelProc etc).
We can also speed-up function call:
function T8BitImageIterator.ReadNextAsGrayscale: Cardinal;
asm
jmp ReadNextPixel;
end;
so after executing ReadNextPixel
we won't come back to ReadNextAsGrayscale
, but return to place where ReadNextPixel
was called..
But all these solutions don't seem right: I see no reason why couldn't 2 VMT entries have the same function pointer, so there would be only one copy of this function which is called 'by any other name' without extra cost. Is it possible to make it so?
It can be done with interfaces, but they are much slower then virtual functions calls
Not really. Interfaces are nothing but virtual function calls, so the performance in calling your functions should be the same as the code you have shown.
Maybe you are thinking that the reference counting is the bottleneck instead. You can disable reference counting, either in the interface implementation classes themselves, or at the call sites where interface pointers are being passed around.
Interfaces are the solution to your problem. They provide an explicit syntax to do exactly what you are asking for, using a Method Resolution Clause. For example:
type
IImageIterator = interface
function ReadNextPixel: Cardinal;
function ReadNextAsGrayscale: Cardinal;
function ReadNextSubpixel: Cardinal;
end;
TImageIteratorBase = class(TInterfacedObject, IImageIterator)
protected
fCurLine, fCurPos: Integer;
public
//basic constructors here, etc.
function DoReadNext: Cardinal; virtual; abstract;
function IImageIterator.ReadNextPixel = DoReadNext;
function IImageIterator.ReadNextAsGrayscale = DoReadNext;
function IImageIterator.ReadNextSubpixel = DoReadNext;
end;
T1BitImageIterator = class(TImageIteratorBase, IImageIterator)
public
function DoReadNext: Cardinal; override;
// implement IImageIterator methods if needed...
end;
T4BitImageIterator = class(TImageIteratorBase, IImageIterator)
public
function DoReadNext: Cardinal; override;
// implement IImageIterator methods if needed...
end;
T8BitImageIterator = class(TImageIteratorBase, IImageIterator)
public
function DoReadNext: Cardinal; override;
// implement IImageIterator methods if needed...
end;
T24BitRGBImageIterator = class(TImageIteratorBase, IImageIterator)
public
function DoReadNext: Cardinal; override;
// implement IImageIterator methods if needed...
end;
...
function T1BitImageIterator.DoReadNext: Cardinal;
begin
//...
end;
function T4BitImageIterator.DoReadNext: Cardinal;
begin
//...
end;
function T8BitImageIterator.DoReadNext: Cardinal;
begin
Result:=fByteLine[fCurPos];
inch(fCurPos);
if fCurPos=fRight then begin
//going to next scanline, checking for end of iterated area etc
end;
end;
function T24BitImageIterator.DoReadNext: Cardinal;
begin
//...
end;
All of the interface methods funnel through the single DoReadNext()
method, which descendants can override
as needed. If a derived class wants to implement the interface methods differently, it can simply implement the desired method(s) directly, ignoring the resolution clauses in the base class. For example:
type
...
T24BitRGBImageIterator = class(TImageIteratorBase, IImageIterator)
public
function DoReadNext: Cardinal; override;
// implement IImageIterator methods if needed...
function ReadNextAsGrayscale: Cardinal;
end;
function T24BitRGBImageIterator.DoReadNext: Cardinal;
begin
//...
end;
function T24BitRGBImageIterator.ReadNextAsGrayscale: Cardinal;
begin
//...
end;
You cannot do the same with classes by themselves, there is no syntax for that. You would have to hack their VMTs directly at runtime, such as at program startup.
Declare a private inline method that does the work, and call it from each of the three virtual methods. The inline engine sometimes does not produce the most efficient code so do check how it performs by inspecting the generated code.
This approach duplicates the code in the executable, but avoids doing so in the source code. A clever compiler/linker would be able to remove this duplication but the Delphi tooling has never had such capability. It would be perfectly possible to hack the VMT but I personally wouldn't go that route. I don't think you'll achieve a discernible gain.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With