PySide Bugzilla Closed for New Bugs

PySide is now a Qt Add-on and uses the Qt Project's JIRA Bug Tracker instead of this Bugzilla instance. This Bugzilla is left for reference purposes.

Bug 566 - 'PySide.QtGui.QImage' object has no attribute 'scanLine'
: 'PySide.QtGui.QImage' object has no attribute 'scanLine'
Status: CLOSED FIXED
Product: PySide
Classification: Unclassified
Component: QtGui
: HEAD
: All All
: P4 normal
Assigned To: renato filho
:
:
:
  Show dependency treegraph
 
Reported: 2010-12-22 20:04 EET by Thomas Perl
Modified: 2011-02-02 15:46 EET (History)
9 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Thomas Perl 2010-12-22 20:04:01 EET
The "scanLine()" function of QImage is the preferred way to manipulate the
pixel data efficiently: http://doc.trolltech.com/latest/qimage.html#scanLine

It would be nice if there was a way to access that functionality from Python as
well, because setPixel() and pixel() are not really that efficient if done on
the whole image.

The "setPixel()" documentation says: "Warning: This function is expensive due
to the call of the internal detach() function called within; if performance is
a concern, we recommend the use of scanLine() to access pixel data directly."

Some ideas: Return a list of QColor values. Can these be modified directly? If
not, provide a way to get a list of QColor values (i.e. scanLine()) and another
method to update a given scanline with a new list of QColor values (i.e.
setScanLine()):

pixels = img.scanLine(0)
img.setScanLine(0, [pixel.darker() for pixel in pixels])

Another suggestion would be to provide a map function that takes a QRgb (or
QColor?) value and returns it, and that function would be called for each
pixel:

def make_darker(color):
    return color.darker()
img.scanLine(0, make_darker)
Comment 1 Matti Airas 2010-12-23 08:24:44 EET
Thanks for the bug report!

This is weird, I thought these sort of completely missing functions would've
already been dealt a few months ago when we did a comparison to PyQt.

I'm prioritizing this P... 2.
Comment 2 Hugo Parente Lima 2010-12-30 16:17:37 EET
IMO the better suggestion was about the signature:

QImage.scanLine(line, lambda)

There's just a problem, what argument this lambda should get?
If we use QRgba the function will be as slow as setPixel/getPixel method.

PyQt4 returns a sip.voidptr object which is near useless too.
Comment 3 Thomas Perl 2010-12-30 17:18:58 EET
(In reply to comment #2)
> IMO the better suggestion was about the signature:
> 
> QImage.scanLine(line, lambda)
> 
> There's just a problem, what argument this lambda should get?
> If we use QRgba the function will be as slow as setPixel/getPixel method.

Would a tuple or something be faster? Or something from ctypes?

Otherwise (but that is most likely overkill) there could be an easy way to
specify the per-pixel operation as C function and call that "on the C level" -
even if that might involve compiling some C code as Python extension module.
Comment 4 Hugo Parente Lima 2010-12-30 18:06:06 EET
We could pass a str object in the lambda e get a str object back, still much
slower than the C++ version because in Python this operation involves memory
allocation for the str object and the copy of the str returned by the lambda to
the uchar pointer returned by scanLine, despite of this detail, this method is
at least generic enough to be used by any image format.
Comment 5 Thomas Perl 2010-12-30 18:17:47 EET
(In reply to comment #4)
> We could pass a str object in the lambda e get a str object back, still much
> slower than the C++ version because in Python this operation involves memory
> allocation for the str object and the copy of the str returned by the lambda to
> the uchar pointer returned by scanLine, despite of this detail, this method is
> at least generic enough to be used by any image format.

Sounds good to me, as long as it's faster than getPixel/setPixel. So the Python
code has to be alignment- and format-aware (e.g. RGB32) and deal with that, but
just the string gets manipulated? Would an iterable (one char per get) as input
and a generator as output (returning each char modified) work faster there
instead of allocating and copying the memory at once, or is that slower,
because of all the switches between "Python land" and "C++ land" when iterators
and generators are used?

By the way: A specific use case that I can think of is "desaturate an image and
make it lighter", just in case you need something to test it with.
Comment 6 Hugo Parente Lima 2010-12-30 19:33:57 EET
One char per get represent 4 loop iterations per pixel, so it's very slow
indeed due to various context switches and object creation/destruction per
iteration.

Even with the string approach this still slow, because strings are immutable
objects in python, so when you are processing the line probably you will create
N strings before you get the final result, when N is the image width. :-/
Comment 7 Thomas Perl 2011-01-01 20:40:01 EET
(In reply to comment #6)
> Even with the string approach this still slow, because strings are immutable
> objects in python, so when you are processing the line probably you will create
> N strings before you get the final result, when N is the image width. :-/

Would the string approach still be significantly faster than the
getPixel/setPixel method?
Comment 8 Hugo Parente Lima 2011-01-03 14:55:58 EET
(In reply to comment #7)
> Would the string approach still be significantly faster than the
> getPixel/setPixel method?

I think only a benchmark would answer that question.
Comment 9 Hugo Parente Lima 2011-01-05 10:35:56 EET
Matti, would you lower the bug priority until we achieve a resonable solution
for the problem?

P3 or P4, I'm not sure...
Comment 10 Matti Airas 2011-01-05 10:43:53 EET
(In reply to comment #9)
> Matti, would you lower the bug priority until we achieve a resonable solution
> for the problem?
> 
> P3 or P4, I'm not sure...

OK - I'll make it P4 for now. I'll increase the priority right away once
there's a clear plan for this. So, keep the discussion alive. :-)
Comment 11 Kenneth Arnold 2011-01-27 16:15:06 EET
I've just made a related comment on bug 565. Numpy already provides efficient
mechanisms for working with exactly this sort of data, and my team has deployed
solutions with PyQt using such a mechanism. (We create a QImage from a Numpy
array to begin with, so we don't need scanLine, but for image manipulation
applications it would be convenient.)

To achieve feature parity with PyQt regarding QImage manipulation would require
implementing something analogous to sip.voidptr that supports the Python buffer
interface or wraps other objects that do.

To avoid completely missing other functionality, I'd suggest documenting all of
the `remove=` methods in the typesystem, categorizing them into ones where the
functionality is already covered by a different mechanism (and document that
mechanism) vs methods that are simply not implemented yet. Stubbing out missing
methods would help people who need that functionality be able to implement it
without figuring out the whole typesystem machinery.
Comment 12 Hugo Parente Lima 2011-01-27 16:37:13 EET
What about making QImage implement the buffer protocol?
bits() and scanLine could use PyBuffer_FromReadWriteMemory to return a Python
buffer.

What do you think?
Comment 13 Kenneth Arnold 2011-01-28 16:25:58 EET
(In reply to comment #12)
> What about making QImage implement the buffer protocol?
> bits() and scanLine could use PyBuffer_FromReadWriteMemory to return a Python
> buffer.
> 
> What do you think?

Is there any case where the internal memory for an image is reallocated? That
would invalidate an object returned from, e.g., bits(), and either silently
stop working or write to freed memory depending on how the memory management
works. To make the QImage _itself_ implement the buffer protocol could avoid
that case for bits(), but scanLine() would be tricky.

(bits() is a misnomer; you're really accessing the bytes.)

A test case for this whole interface would be a resizeable interactive demo
(e.g., Qt's scribble demo) where the image is altered in-place by numpy.
Ideally it would even work on Python 2.6 :)
Comment 14 Hugo Parente Lima 2011-01-28 17:46:35 EET
(In reply to comment #13)
> (In reply to comment #12)
> > What about making QImage implement the buffer protocol?
> > bits() and scanLine could use PyBuffer_FromReadWriteMemory to return a Python
> > buffer.
> > 
> > What do you think?
> 
> Is there any case where the internal memory for an image is reallocated? That
> would invalidate an object returned from, e.g., bits(), and either silently
> stop working or write to freed memory depending on how the memory management
> works. To make the QImage _itself_ implement the buffer protocol could avoid
> that case for bits(), but scanLine() would be tricky.

I did the following:

The constructors accepts any object that implements the buffer protocol, when
you use these constructors you need to keep the buffer alive for the lifetime
of the QImage like C++ do, in other words it doesn't copy the image data.

bits and scanLine also returns buffer objects "without" copying any data, I
wrote the quotes because when you call bits() or scanLine() PySide will call
the non-const version of those methods causing QImage will detach (see QImage
docs). To avoid this memory copy you must call constBits() or constScanLine()

I'll do the merge request soon, the merge will also fix bug 566, if you have
any suggestion the time is now. =]

> (bits() is a misnomer; you're really accessing the bytes.)
> 
> A test case for this whole interface would be a resizeable interactive demo
> (e.g., Qt's scribble demo) where the image is altered in-place by numpy.
> Ideally it would even work on Python 2.6 :)
Comment 15 Kenneth Arnold 2011-01-28 18:54:15 EET
(In reply to comment #14)
> The constructors accepts any object that implements the buffer protocol

Old or new buffer protocol (or both)?

> I'll do the merge request soon, the merge will also fix bug 566, if you have
> any suggestion the time is now. =]

Excellent! I don't need bits() or scanLine() so I can't speak to whether that
behavior is appropriate, but constructing from buffer-protocol objects like
numpy arrays is great news.
Comment 16 Hugo Parente Lima 2011-01-28 19:04:37 EET
Old, because we need to support python 2.5, Maemo still using it :-/.
Comment 17 Hugo Parente Lima 2011-01-31 14:50:07 EET
Fixed in commit:
pyside/2a4391902406552815140f4ebe078f9f7e9a955c

scanLine(), constScanLine(), bits() and constBits() returns memory buffers.
Comment 18 renato filho 2011-02-02 15:46:06 EET
released on beta 5