Screenshot [ctypes.windll CreateDCFromHandle] - python

I am creating a screenshot module using only pure python (ctypes), no big lib like win32, wx, QT, ... It has to manage multi-screens (what PIL and Pillow cannot).
Where I am blocking is when calling CreateDCFromHandle, ctypes.windll.gdi32 does not know this function. I looked at win32 source code to being inspired, but useless. As said in comment, this function does not exist in the MSDN, so what changes should I apply to take in consideration other screens?
This is the code which works for the primary monitor, but not for others: source code.
It is blocking at the line 35. I tried a lot of combinations, looking for answers here and on others websites. But nothing functional for me ... It is just a screenshot!
Do you have clues?
Thanks in advance :)
Edit, I found my mystake! This is the code that works:
srcdc = ctypes.windll.user32.GetWindowDC(0)
memdc = ctypes.windll.gdi32.CreateCompatibleDC(srcdc)
bmp = ctypes.windll.gdi32.CreateCompatibleBitmap(srcdc, width, height)
ctypes.windll.gdi32.SelectObject(memdc, bmp)
ctypes.windll.gdi32.BitBlt(memdc, 0, 0, width, height, srcdc, left, top, SRCCOPY)
bmp_header = pack('LHHHH', calcsize('LHHHH'), width, height, 1, 24)
c_bmp_header = c_buffer(bmp_header)
c_bits = c_buffer(' ' * (height * ((width * 3 + 3) & -4)))
got_bits = ctypes.windll.gdi32.GetDIBits(memdc, bmp, 0, height,
c_bits, c_bmp_header, DIB_RGB_COLORS)
# Here, got_bits should be equal to height to tell you all goes well.
French article with full explanations : Windows : capture d'écran

Edit, I found my mystake! This is the code that works:
srcdc = ctypes.windll.user32.GetWindowDC(0)
memdc = ctypes.windll.gdi32.CreateCompatibleDC(srcdc)
bmp = ctypes.windll.gdi32.CreateCompatibleBitmap(srcdc, width, height)
ctypes.windll.gdi32.SelectObject(memdc, bmp)
ctypes.windll.gdi32.BitBlt(memdc, 0, 0, width, height, srcdc, left, top, SRCCOPY)
bmp_header = pack('LHHHH', calcsize('LHHHH'), width, height, 1, 24)
c_bmp_header = c_buffer(bmp_header)
c_bits = c_buffer(' ' * (height * ((width * 3 + 3) & -4)))
got_bits = ctypes.windll.gdi32.GetDIBits(
memdc, bmp, 0, height, c_bits, c_bmp_header, DIB_RGB_COLORS)
# Here, got_bits should be equal to height to tell you all goes well.

This isn't a Windows API function. You will need a combination of EnumDisplayDevices and CreateDC. Be aware that you must append "A" or "W" to the names of the functions depending on if you want to use ANSI strings or Unicode (widechar) strings.

Looking at the source for pywin32, CreateDCFromHandle is a fabrication. It does not exist in the Windows API; it is simply a bridge converting a Windows API thing into a pywin32 thing.
Since you're using ctypes rather than pywin32, no conversion is necessary; see if you can skip that step:
hwin = user.GetDesktopWindow()
hwindc = user.GetWindowDC(monitor['hmon'])
memdc = gdi.CreateCompatibleDC(hwindc)
When you're trying to do some native-Windows API thing with ctypes in Python, I find it more helpful to look at existing C code which already uses the Windows API rather than using Python code that uses a wrapper around it.

Related

Colorize parts of a complex equation manim

I want to paint variables of MathTex element in different colors, but Manim seems to have problems with comlicated Latex expressions.
Here is my scene.
from manim import *
config.frame_width = 260
class Find_Path(Scene):
def construct(self):
obj = MathTex(r"minimize \quad \sum_{start}^{end}\frac{d_{i,i+1}}{v_{i,i+1}}",
font_size=1000, substrings_to_isolate="d" and "v")
obj.set_color_by_tex("d", YELLOW)
obj.set_color_by_tex("start", GREEN)
obj.set_color_by_tex("end", GREEN)
obj.set_color_by_tex("v", RED)
self.play(Write(obj))
self.wait(3)
Here is the result.
Specifically, I want to color d_{i,i+1} in YELLOW, v_{i,i+1} in RED, start and end in GREEN.
Any advice? Frankly, I do not want to create several MathTex object in different colors and then arrange them.
Manim does a bunch of tex rewriting under the covers, and it seems that over is preferred to frac because of that rewriting.
I was able to apply the colors that you wanted (although I suspect you didn't want the sum symbol colored) with:
from manim import *
class Find_Path(Scene):
def construct(self):
obj1 = MathTex(r"\text{minimize}", r"\quad \sum_{\text{start}}^{\text{end}}")
obj2 = MathTex(r"d_{i,i+1}", r"\over", r"v_{i,i+1}")
obj1.set_color_by_tex("start", GREEN)
obj1.set_color_by_tex("end", GREEN)
obj2.move_to(obj1, RIGHT)
obj2.shift(1.5 * RIGHT)
obj2[0].set_color(YELLOW)
obj2[2].set_color(RED)
self.play(AnimationGroup(Write(obj1), Write(obj2)))
self.wait(3)
but I had to resort to separate objects. Worse still, I aligned them by hand with a fudge factor.
Late answer, but I encountered a similar issue and ended up here before finding the relevant section in the documentation.
Relevant section in documentation: Using index_labels to work with complicated strings
An example with your special case:
from manim import *
config.frame_width = 8
config.frame_size = (1300, 1000)
class FindPath(Scene):
def construct(self):
# You can split the string in parts
minimize = r"minimize \quad "
summ = r"\sum_{start}^{end}"
frac = r"\frac{d_{i,i+1}}{v_{i,i+1}}"
tex = MathTex(minimize, summ, frac).shift(2 * UP)
# Observe first level labels
tex_ = tex.copy().next_to(tex, DOWN)
self.add(index_labels(tex_, color=YELLOW))
# Observe second level labels
tex__ = tex_.copy().next_to(tex_, DOWN)
for part in tex__:
self.add(index_labels(part, color=YELLOW))
# Finally you can color accordingly
tex[1][0:3].set_fill(color=GREEN)
tex[1][4:9].set_fill(color=GREEN)
tex[2][0:6].set_fill(color=YELLOW)
tex[2][7:13].set_fill(color=RED)
self.add(tex, tex_, tex__)

vPython / GlowScript Sensor Position

I'm showing some strange discrepancies between my declared camera position (scene.camera.pos), and the actual camera position. I can't believe this feature is just broken, am I missing something here?
Here's the code, and the output shown below
GlowScript 3.1 VPython
cube = box(pos=vector(0, 0, 0), size=vector(1,1,1), color=color.red, texture=textures.rough)
scene.lights = [distant_light(direction=vector(0.4226, 0, -0.9063),color=color.gray(0.7)),distant_light(direction=vector(0.4226, 0, -0.9063),color=color.gray(0.7))]
scene.background = color.gray(0.8)
scene.camera.pos = vector(3,3,-3)
scene.camera.axis = cube.pos - scene.camera.pos
#scene.forward=cube.pos
#scene.camera.center=cube.pos
#scene.camera.fov = (pi/180)*10
#scene.camera.axis = vector(0, 0, 0)
#scene.up = vector(0,1,0)
while True:
rate(0.5)
scene.append_to_title(scene.camera.pos)
#scene.camera.rotate(angle=0.05, axis=vec(0,0,1), origin=vec(0,10,0))
#scene.capture("woah")
I think I see the problem. There is a conflict between you manipulating the camera and the default scene.autoscale = True. If you set scene.autoscale = False before manipulating the camera, I think you'll find that the program behaves as expected. At the very least, this implies a need for the camera documentation to point out this conflict.
Finally checked back in on this and it's working properly now.

Reportlab [ Platypus ] - Image does not render

I am working on a report that includes a mixture of tables and images. The images [ graphs, actually ] are saved to the filesystem in .png form.
The method that actually renders the PDF is:
def _render_report(report_data):
file_name = get_file_name() # generate random filename for report
rpt = Report(settings.MEDIA_ROOT + os.sep + file_name)
Story = []
for (an, sam, event), props in report_data.iteritems():
Story.append(Paragraph("%s - sample %s results for %s" % (an.name, sam.name, event.name), styles["Heading2"]))
data_list = [['Lab', 'Method', 'Instrument', 'Unit', 'Raw Result', 'Converted Result', 'Outlier']]
for (index, series) in props['frame'].iterrows():
data_list.append(_format([
Paragraph(Lab.objects.get(pk=series['labs']).name, styles['BodyText']),
Paragraph(Method.objects.get(pk=series['methods']).name, styles['BodyText']),
Paragraph(Instrument.objects.get(pk=series['instruments']).name, styles['BodyText']),
Paragraph(Unit.objects.get(pk=series['units']).name, styles['BodyText']),
series['raw_results'],
series['results'],
series['outlier']
]))
table = Table(data_list, colWidths=[45 * mm, 35 * mm, 35 * mm, 25 * mm, 35 * mm, 35 * mm, 35 * mm], repeatRows=1)
Story.append(table)
Story.append(PageBreak())
if props['graph'] is not None:
Story.append(Image("/tmp/%s" % props['graph'], width=10 * inch, height=6 * inch))
Story.append(PageBreak())
rpt.draw(Story, onFirstPage=setup_header_and_footer, onLaterPages=setup_header_and_footer)
return file_name
Background Information
The page is set up as an A4, in landscape orientation
My development environment is a virtualenv; PIL 1.1.7 and reportlab 2.6 are installed and functional
The "Report" class used above is simply a thin wrapper around SimpleDocTemplate that sets up some defaults but delegates to SimpleDocTemplate's build implementation. Its code is:
class Report(object):
def __init__(self, filename, doctitle="Report", docauthor="<default>",
docsubject="<default>", doccreator="<default>", orientation="landscape", size=A4):
DEFAULTS = {
'leftMargin' : 10 * mm,
'rightMargin' : 10 * mm,
'bottomMargin' : 15 * mm,
'topMargin' : 36 * mm,
'pagesize' : landscape(size) if orientation == "landscape" else portrait(size),
'title' : doctitle,
'author' : docauthor,
'subject' : docsubject,
'creator' : doccreator
}
self.doc = SimpleDocTemplate(filename, **DEFAULTS)
def draw(self, flowables, onFirstPage=setup_header_and_footer, onLaterPages=setup_header_and_footer):
self.doc.build(flowables, onFirstPage=setup_header_and_footer,
onLaterPages=setup_header_and_footer, canvasmaker=NumberedCanvas)
What I have already looked at
I have confirmed that the images exist on disk. The paths are fully qualified paths.
PIL is installed, and is able to read the images correctly
The space assigned to the image is adequate; I have confirmed this by calculation. Also, if I increase the image size, ReportLab complains about the Image Flowable being too large. The current dimension should fit.
I have tested with and without the page breaks; they do not seem to make any difference
The Problem
The table, headings and page templates render OK but the images are blank. Earlier today, I had encountered this issue [ when setting up the templates used by this report ]. The workaround was to use canvas.drawInlineImage(... in place of canvas.DrawImage(... . It therefore looks as though there is an issue with my setup; I could use some pointers on how to debug it.
Update
I was able to apply a variant of the same workaround used in this linked question ( use canvas.drawInlineImage in place of canvas.drawImage. I subclassed `Image' as follows:
class CustomImage(Image):
"""
Override - to use inline image instead; inexplicable bug with non inline images
"""
def draw(self):
lazy = self._lazy
if lazy>=2: self._lazy = 1
self.canv.drawInlineImage(self.filename,
getattr(self,'_offs_x',0),
getattr(self,'_offs_y',0),
self.drawWidth,
self.drawHeight
)
if lazy>=2:
self._img = None
self._lazy = lazy
The only change from the "stock" Image class is in one line - using self.canv.drawInlineImage where there was self.canvas.drawImage before.
This "works" in the sense that the images are finally visible in my PDF. The reason why drawImage is not working still remains a mystery.
I have tried #PedroRomano's suggestion ( to make sure the images RGBA ), and even tried JPEG images instead of PNGs. These did not make a difference.
I eventually brought this matter to a close by using a custom Image subclass:
class CustomImage(Image):
"""
Override - to use inline image instead; inexplicable bug with non inline images
"""
def draw(self):
lazy = self._lazy
if lazy>=2: self._lazy = 1
self.canv.drawInlineImage(self.filename,
getattr(self,'_offs_x',0),
getattr(self,'_offs_y',0),
self.drawWidth,
self.drawHeight
)
if lazy>=2:
self._img = None
self._lazy = lazy
Saving the graphs in vector graphics formats eg EPS, saving as JPEG, saving as PNG with and without the alpha channel all did not seem to make a difference.
The optimal solution would be to generate your graphs in a vector format like postscript that is supported by reportlab. A lot of UNIX software can do this out of the box, and on windows you can use the excellent PDFCreator.
If you have to use raster images for your graphs, try converting your images to JPEG format. Those can be easily embedded in a PDF file using the DCTDecode filter. (This is e.g. what jpeg2pdf does.)

Python Get Screen Pixel Value in OS X

I'm in the process of building an automated game bot in Python on OS X 10.8.2 and in the process of researching Python GUI automation I discovered autopy. The mouse manipulation API is great, but it seems that the screen capture methods rely on deprecated OpenGL methods...
Are there any efficient ways of getting the color value of a pixel in OS X? The only way I can think of now is to use os.system("screencapture foo.png") but the process seems to have unneeded overhead as I'll be polling very quickly.
A small improvement, but using the TIFF compression option for screencapture is a bit quicker:
$ time screencapture -t png /tmp/test.png
real 0m0.235s
user 0m0.191s
sys 0m0.016s
$ time screencapture -t tiff /tmp/test.tiff
real 0m0.079s
user 0m0.028s
sys 0m0.026s
This does have a lot of overhead, as you say (the subprocess creation, writing/reading from disc, compressing/decompressing).
Instead, you could use PyObjC to capture the screen using CGWindowListCreateImage. I found it took about 70ms (~14fps) to capture a 1680x1050 pixel screen, and have the values accessible in memory
A few random notes:
Importing the Quartz.CoreGraphics module is the slowest part, about 1 second. Same is true for importing most of the PyObjC modules. Unlikely to matter in this case, but for short-lived processes you might be better writing the tool in ObjC
Specifying a smaller region is a bit quicker, but not hugely (~40ms for a 100x100px block, ~70ms for 1680x1050). Most of the time seems to be spent in just the CGDataProviderCopyData call - I wonder if there's a way to access the data directly, since we dont need to modify it?
The ScreenPixel.pixel function is pretty quick, but accessing large numbers of pixels is still slow (since 0.01ms * 1650*1050 is about 17 seconds) - if you need to access lots of pixels, probably quicker to struct.unpack_from them all in one go.
Here's the code:
import time
import struct
import Quartz.CoreGraphics as CG
class ScreenPixel(object):
"""Captures the screen using CoreGraphics, and provides access to
the pixel values.
"""
def capture(self, region = None):
"""region should be a CGRect, something like:
>>> import Quartz.CoreGraphics as CG
>>> region = CG.CGRectMake(0, 0, 100, 100)
>>> sp = ScreenPixel()
>>> sp.capture(region=region)
The default region is CG.CGRectInfinite (captures the full screen)
"""
if region is None:
region = CG.CGRectInfinite
else:
# TODO: Odd widths cause the image to warp. This is likely
# caused by offset calculation in ScreenPixel.pixel, and
# could could modified to allow odd-widths
if region.size.width % 2 > 0:
emsg = "Capture region width should be even (was %s)" % (
region.size.width)
raise ValueError(emsg)
# Create screenshot as CGImage
image = CG.CGWindowListCreateImage(
region,
CG.kCGWindowListOptionOnScreenOnly,
CG.kCGNullWindowID,
CG.kCGWindowImageDefault)
# Intermediate step, get pixel data as CGDataProvider
prov = CG.CGImageGetDataProvider(image)
# Copy data out of CGDataProvider, becomes string of bytes
self._data = CG.CGDataProviderCopyData(prov)
# Get width/height of image
self.width = CG.CGImageGetWidth(image)
self.height = CG.CGImageGetHeight(image)
def pixel(self, x, y):
"""Get pixel value at given (x,y) screen coordinates
Must call capture first.
"""
# Pixel data is unsigned char (8bit unsigned integer),
# and there are for (blue,green,red,alpha)
data_format = "BBBB"
# Calculate offset, based on
# http://www.markj.net/iphone-uiimage-pixel-color/
offset = 4 * ((self.width*int(round(y))) + int(round(x)))
# Unpack data from string into Python'y integers
b, g, r, a = struct.unpack_from(data_format, self._data, offset=offset)
# Return BGRA as RGBA
return (r, g, b, a)
if __name__ == '__main__':
# Timer helper-function
import contextlib
#contextlib.contextmanager
def timer(msg):
start = time.time()
yield
end = time.time()
print "%s: %.02fms" % (msg, (end-start)*1000)
# Example usage
sp = ScreenPixel()
with timer("Capture"):
# Take screenshot (takes about 70ms for me)
sp.capture()
with timer("Query"):
# Get pixel value (takes about 0.01ms)
print sp.width, sp.height
print sp.pixel(0, 0)
# To verify screen-cap code is correct, save all pixels to PNG,
# using http://the.taoofmac.com/space/projects/PNGCanvas
from pngcanvas import PNGCanvas
c = PNGCanvas(sp.width, sp.height)
for x in range(sp.width):
for y in range(sp.height):
c.point(x, y, color = sp.pixel(x, y))
with open("test.png", "wb") as f:
f.write(c.dump())
I came across this post while searching for a solution to get screenshot in Mac OS X used for real-time processing. I have tried using ImageGrab from PIL as suggested in some other posts but couldn't get the data fast enough (with only about 0.5 fps).
The answer https://stackoverflow.com/a/13024603/3322123 in this post to use PyObjC saved my day! Thanks #dbr!
However, my task requires to get all pixel values rather than just a single pixel, and also to comment on the third note by #dbr, I added a new method in this class to get a full image, in case anyone else might need it.
The image data are returned as a numpy array with dimension of (height, width, 3), which can be directly used for post-processing in numpy or opencv etc… getting individual pixel values from it also becomes pretty trivial using numpy indexing.
I tested the code with a 1600 x 1000 screenshot - getting the data using capture() took ~30 ms and converting it to a np array getimage() takes only ~50 ms on my Macbook. So now I have >10 fps and even faster for smaller regions.
import numpy as np
def getimage(self):
imgdata=np.fromstring(self._data,dtype=np.uint8).reshape(len(self._data)/4,4)
return imgdata[:self.width*self.height,:-1].reshape(self.height,self.width,3)
note I throw away the “alpha” channel from the BGRA 4 channel.
This was all so very helpful I had to come back to comment / however I don't have the reputation.. I do, however, have a sample code of a combination of the answers above for a lightning quick screen capture / save thanks to #dbr and #qqg!
import time
import numpy as np
from scipy.misc import imsave
import Quartz.CoreGraphics as CG
image = CG.CGWindowListCreateImage(CG.CGRectInfinite, CG.kCGWindowListOptionOnScreenOnly, CG.kCGNullWindowID, CG.kCGWindowImageDefault)
prov = CG.CGImageGetDataProvider(image)
_data = CG.CGDataProviderCopyData(prov)
width = CG.CGImageGetWidth(image)
height = CG.CGImageGetHeight(image)
imgdata=np.fromstring(_data,dtype=np.uint8).reshape(len(_data)/4,4)
numpy_img = imgdata[:width*height,:-1].reshape(height,width,3)
imsave('test_fast.png', numpy_img)

QPixmap maintain aspect ratio

I'm writing a program that will allow me to upload photos to TUMBLR via their API, I've got the uploading working (thanks to you guys).
I've put a 'queueBox' on the side of the GUI, which displays the image names, and they are stored in a QListWidget. I've put this in my Main Class' constructor:
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setupUi(self)
self.queueBox.itemClicked.connect(self.displayPhoto)
and I have this method:
def displayPhoto(self, item):
tempName = (item.text())
print tempName
self.myLabel.setPixmap(QtGui.QPixmap(_fromUtf8(directory + '\\' + tempName)))
## self.myLabel.pixmap(QPixmap.scaled(aspectRatioMode = Qt.IgnoreAspectRatio))
## ^ ^ ^ What do I do with this? How do I set it to maintain aspect ratio?
## Currently it says ''NameError: global name 'Qt' is not defined''
This sucessfully draws the image on to myLabel which is a QLabel, however, It is very scaled, I have
self.myLabel.setScaledContents(True)
in my ui_mainWindow class, and if I turn it to False, it fixes the scaling but it only shows a small portion of the image because the image is much larger than the QLabel. What I want is to be able to maintain the aspect ratio, so it doesn't look scaled and horrible.
I found this: http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qpixmap.html
and it says how to use it, however I can't get it to work as shown in the code above in my comments. Does anyone know how to use this? If so, can you provide me with an example, I've tried searching but most of the results I get are working examples in C++, not python.
Thanks!
Get rid of the
self.myLabel.setScaledContents(True)
call (or set it to False). It is filling your widget with the pixmap without caring about the aspect ratio.
If you need to resize a QPixmap, as you have found, scaled is the required method. But you are invoking it wrong. Let's look at the definition:
QPixmap QPixmap.scaled (self,
int width,
int height,
Qt.AspectRatioMode aspectRatioMode = Qt.IgnoreAspectRatio,
Qt.TransformationMode transformMode = Qt.FastTransformation)
Return type of this function is QPixmap, so it returns a scaled copy of the original pixmap.
Then you need a width and a height, describing the (maximum) final size of the pixmap.
Two more optional parameters. aspectRatioMode deals with the, well aspect ratio. The documentation details the different options and their effects. transformMode defines how (which algorithm) the scaling is done. It might change the final quality of your image. You probably don't need this one.
So, putting it together you should have (Qt namespace is inside QtCore):
# substitute the width and height to desired values
self.myLabel.setPixmap(QtGui.QPixmap(_fromUtf8(directory + '\\' + tempName)).scaled(width, height, QtCore.Qt.KeepAspectRatio))
Alternatively, if you have a fixed size QLabel, you could call the .size() method to get the size from it:
self.myLabel.setPixmap(QtGui.QPixmap(_fromUtf8(directory + '\\' + tempName)).scaled(self.myLabel.size(), QtCore.Qt.KeepAspectRatio))
Note: You might want to use os.path.join(directory, tempName) for the directory + '\\' + tempName part.
PyQt5 code change update:
The above answer of avaris needed a PyQt5 update because it breaks.
QPixmap.scaled (self, int width, int height, Qt.AspectRatioMode aspectRatioMode = Qt.IgnoreAspectRatio
Keeping the self in the code results in below traceback error.
TypeError: arguments did not match any overloaded call: scaled(self, int, int, aspectRatioMode: Qt.AspectRatioMode = Qt.IgnoreAspectRatio, transformMode: Qt.TransformationMode = Qt.FastTransformation): argument 1 has unexpected type 'MainUI' scaled(self, QSize, aspectRatioMode: Qt.AspectRatioMode = Qt.IgnoreAspectRatio, transformMode: Qt.TransformationMode = Qt.FastTransformation): argument 1 has unexpected type 'MainUI'
Thus this should be (without "self", "Qt") as stated below:
QPixmap.scaled (int width, int height, aspectRatioMode = IgnoreAspectRatio
or:
QPixmap.scaled (int width, int height, aspectRatioMode = 0)
KeepAspectRatio = 2... but used as provided by aspectRatioMode = 2 in above code. Enjoy!

Categories