#!/usr/bin/env python2
|
|
|
|
# Gnome15 - Suite of tools for the Logitech G series keyboards and headsets
|
|
# Copyright (C) 2011 Brett Smith <tanktarta@blueyonder.co.uk>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
Simple tool to draw an image on the framebuffer
|
|
"""
|
|
|
|
|
|
import sys
|
|
import os
|
|
import glib
|
|
import cairo
|
|
import array
|
|
from PIL import Image
|
|
from PIL import ImageMath
|
|
from cStringIO import StringIO
|
|
|
|
# Allow running from local path
|
|
path = os.path.abspath(os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), ".."))
|
|
if os.path.exists(path):
|
|
sys.path.insert(0, path)
|
|
|
|
import gnome15.util.g15convert as g15convert
|
|
import gnome15.util.g15cairo as g15cairo
|
|
import gnome15.drivers.fb as fb
|
|
|
|
if __name__ == "__main__":
|
|
import optparse
|
|
parser = optparse.OptionParser()
|
|
parser.add_option("-s", "--scale", dest="scale", metavar="stretch,zoom,tile,center,scale",
|
|
default="zoom" , help="Scale type")
|
|
parser.add_option("-d", "--device", dest="device",
|
|
default="/dev/fb0" , help="Framebuffer device")
|
|
(options, args) = parser.parse_args()
|
|
bg_style = options.scale
|
|
|
|
# Check arguments
|
|
if len(args) != 1:
|
|
sys.stderr.write("You must provide a single image filenanme")
|
|
sys.exit(1)
|
|
|
|
# Locate and configure the framebuffer
|
|
fb_dev = fb.fb_device(options.device)
|
|
var_info = fb_dev.get_var_info()
|
|
fixed_info = fb_dev.get_fixed_info()
|
|
screen_size = ( var_info.xres, var_info.yres )
|
|
width, height = screen_size
|
|
|
|
# Create an empty string buffer for use with monochrome LCD
|
|
empty_buf = ""
|
|
for i in range(0, fixed_info.smem_len):
|
|
empty_buf += chr(0)
|
|
|
|
# Load the image
|
|
bg_img = args[0]
|
|
if g15cairo.is_url(bg_img) or os.path.exists(bg_img):
|
|
img_surface = g15cairo.load_surface_from_file(bg_img)
|
|
if img_surface is not None:
|
|
sx = float(width) / img_surface.get_width()
|
|
sy = float(height) / img_surface.get_height()
|
|
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
|
|
context = cairo.Context(surface)
|
|
context.save()
|
|
if bg_style == "zoom":
|
|
scale = max(sx, sy)
|
|
context.scale(scale, scale)
|
|
context.set_source_surface(img_surface)
|
|
context.paint()
|
|
elif bg_style == "stretch":
|
|
context.scale(sx, sy)
|
|
context.set_source_surface(img_surface)
|
|
context.paint()
|
|
elif bg_style == "scale":
|
|
x = ( width - img_surface.get_width() * sy ) / 2
|
|
context.translate(x, 0)
|
|
context.scale(sy, sy)
|
|
context.set_source_surface(img_surface)
|
|
context.paint()
|
|
elif bg_style == "center":
|
|
x = ( width - img_surface.get_width() ) / 2
|
|
y = ( height - img_surface.get_height() ) / 2
|
|
context.translate(x, y)
|
|
context.set_source_surface(img_surface)
|
|
context.paint()
|
|
elif bg_style == "tile":
|
|
context.set_source_surface(img_surface)
|
|
context.paint()
|
|
y = 0
|
|
x = img_surface.get_width()
|
|
while y < height + img_surface.get_height():
|
|
if x >= height + img_surface.get_width():
|
|
x = 0
|
|
y += img_surface.get_height()
|
|
context.restore()
|
|
context.save()
|
|
context.translate(x, y)
|
|
context.set_source_surface(img_surface)
|
|
context.paint()
|
|
x += img_surface.get_width()
|
|
|
|
context.restore()
|
|
else:
|
|
sys.stderr.write("Failed to load image file %s." % bg_img)
|
|
sys.exit(1)
|
|
else:
|
|
sys.stderr.write("Image path %s is not a URL or an existing file." % bg_img)
|
|
sys.exit(1)
|
|
|
|
# Convert the image to the required format for this device
|
|
if var_info.bits_per_pixel == 16:
|
|
try:
|
|
back_surface = cairo.ImageSurface (4, width, height)
|
|
except Exception as e:
|
|
logger.debug("Could not create ImageSurface. Trying alternative method", exc_info = e)
|
|
# Earlier version of Cairo
|
|
back_surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, width, height)
|
|
back_context = cairo.Context (back_surface)
|
|
back_context.set_source_surface(surface, 0, 0)
|
|
back_context.set_operator (cairo.OPERATOR_SOURCE);
|
|
back_context.paint()
|
|
|
|
if back_surface.get_format() == cairo.FORMAT_ARGB32:
|
|
"""
|
|
If the creation of the type 4 image failed (i.e. earlier version of Cairo)
|
|
then we have to convert it ourselves. This is slow.
|
|
|
|
TODO Replace with C routine
|
|
"""
|
|
file_str = StringIO()
|
|
data = back_surface.get_data()
|
|
for i in range(0, len(data), 4):
|
|
r = ord(data[i + 2])
|
|
g = ord(data[i + 1])
|
|
b = ord(data[i + 0])
|
|
file_str.write(g15convert.rgb_to_uint16(r, g, b))
|
|
buf = file_str.getvalue()
|
|
else:
|
|
buf = str(back_surface.get_data())
|
|
else:
|
|
arrbuf = array.array('B', empty_buf)
|
|
|
|
argb_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
|
|
argb_context = cairo.Context(argb_surface)
|
|
argb_context.set_source_surface(surface)
|
|
argb_context.paint()
|
|
|
|
'''
|
|
Now convert the ARGB to a PIL image so it can be converted to a 1 bit monochrome image, with all
|
|
colours dithered. It would be nice if Cairo could do this :( Any suggestions?
|
|
'''
|
|
pil_img = Image.frombuffer("RGBA", (width, height), argb_surface.get_data(), "raw", "RGBA", 0, 1)
|
|
pil_img = ImageMath.eval("convert(pil_img,'1')",pil_img=pil_img)
|
|
pil_img = ImageMath.eval("convert(pil_img,'P')",pil_img=pil_img)
|
|
pil_img = pil_img.point(lambda i: i >= 250,'1')
|
|
|
|
# Invert the screen if required
|
|
if options.invert:
|
|
pil_img = pil_img.point(lambda i: 1^i)
|
|
|
|
# Data is 160x43, 1 byte per pixel. Will have value of 0 or 1.
|
|
data = list(pil_img.getdata())
|
|
v = 0
|
|
b = 1
|
|
|
|
# TODO Replace with C routine
|
|
for row in range(0, height):
|
|
for col in range(0, width):
|
|
if data[( row * width ) + col]:
|
|
v += b
|
|
b = b << 1
|
|
if b == 256:
|
|
# Full byte
|
|
b = 1
|
|
i = row * fixed_info.line_length + col / 8
|
|
|
|
if row > 7 and col < 96:
|
|
'''
|
|
????? This was discovered more by trial and error rather than any
|
|
understanding of what is going on
|
|
'''
|
|
i -= 12 + ( 7 * fixed_info.line_length )
|
|
|
|
arrbuf[i] = v
|
|
v = 0
|
|
buf = arrbuf.tostring()
|
|
|
|
# Write to buffer
|
|
fb_dev.buffer[0:len(buf)] = buf
|
|
|