#! /usr/bin/env python # gauge-png.py: create a gauge faceplate for the Asia Engineer 30 volt round # analog panel meter. # copyright 2008 jason pepas (jason@pepas.com) # distributed under the terms of the GNU General Public License, version 2 # see http://www.gnu.org/copyleft/gpl.html # changelog: # # 0.1 (2008/1/5) # * initial release. # * basic faceplate mask. # * dimensions conform to original faceplate. # # 0.2 (2008/1/5) # * tick marks! # # 0.3 (2008/1/6) # * support for sigmoidal scale # # 0.4 (2008/1/19) # * support for labels from math import pi, sin, cos import math import cairo from freetypepython import create_cairo_font_face_for_file # tunables: width = 600 height = width bottom_edge = 0.875 needle_hole_radius = 0.175 needle_hole_y_offset = 0.2125 backlight_radius = 0.525 backlight_arc_height = 0.0875 primary_divisions = 5 secondary_divisions = 10 # you shouldn't need to tune these. outer_radius = 0.5 center_x = 0.5 center_y = 0.5 needle_hole_center_x = center_x needle_hole_center_y = center_y + needle_hole_y_offset backlight_radian_start = -(pi * (3/4.0)) backlight_radian_end = backlight_radian_start + (pi/2) def draw_basic_mask(cr): # white background cr.set_source_rgb(1.0, 1.0, 1.0) cr.rectangle(0, 0, width, height) cr.fill() # we will use subtractive geometry. start with a big black disc and then # remove the bits we don't want. # 1: circle cr.set_source_rgb(0, 0, 0) cr.arc(center_x, center_y, outer_radius, 0, 2 * pi) cr.fill() # 2: bottom square cr.set_source_rgb(1.0, 1.0, 1.0) cr.rectangle(0, bottom_edge, 1, 1) cr.fill() # 3: needle hole circle cr.set_source_rgb(1.0, 1.0, 1.0) cr.arc(needle_hole_center_x, needle_hole_center_y, needle_hole_radius, 0, 2 * pi) cr.fill() # 4: bottom entry way for needle assembly cr.set_source_rgb(1.0, 1.0, 1.0) cr.rectangle(center_x - needle_hole_radius, needle_hole_center_y, needle_hole_radius*2, 1) cr.fill() # 5: backlight arc-hole cr.set_source_rgb(1.0, 1.0, 1.0) cr.arc(needle_hole_center_x, needle_hole_center_y, backlight_radius, backlight_radian_start, backlight_radian_end) cr.set_line_width(backlight_arc_height) cr.stroke() def draw_tick_mark(tick_offset, tick_width, tick_height): #these are actually implemented as very short and stubby arcs. cr.set_source_rgb(0, 0, 0) cr.arc(needle_hole_center_x, \ needle_hole_center_y, \ backlight_radius + (backlight_arc_height/2) - ((backlight_arc_height*tick_height)/2), \ backlight_radian_start + (tick_offset*(pi/2)) - ((tick_width*(pi/2))/2), \ backlight_radian_start + (tick_offset*(pi/2)) + ((tick_width*(pi/2))/2)) cr.set_line_width(backlight_arc_height * tick_height) cr.stroke() def draw_ticks_linear(primary_divisions, secondary_divisions): ticks = (primary_divisions * secondary_divisions) + 1 primary_tick_width = 0.01 primary_tick_height = 2 / 3.0 secondary_tick_width = primary_tick_width / 2.0 secondary_tick_height = primary_tick_height / 2.0 for i in range(0, ticks): tick_offset = i / float(ticks - 1) if i % secondary_divisions == 0: draw_tick_mark(tick_offset, primary_tick_width, primary_tick_height) else: draw_tick_mark(tick_offset, secondary_tick_width, secondary_tick_height) def xfer_func_sigmoidal(input, span=6.0): return 1.0 / (1 + math.pow(math.e, -((span * input) - (span / 2.0)))) def xfer_func_sigmoidal_normalized(input, span=6.0): max = xfer_func_sigmoidal(1.0, span) norm_factor = 0.5 / (max - 0.5) sigmoided = xfer_func_sigmoidal(input, span) normalized = ((sigmoided - 0.5) * norm_factor) + 0.5 return normalized def draw_ticks_sigmoidal(primary_divisions, secondary_divisions, span): ticks = (primary_divisions * secondary_divisions) + 1 primary_tick_width = 0.005 primary_tick_height = 2 / 3.0 secondary_tick_width = primary_tick_width / 2.0 secondary_tick_height = primary_tick_height / 2.0 for i in range(0, ticks): tick_offset = i / float(ticks - 1) orig_offset = tick_offset tick_offset = xfer_func_sigmoidal(tick_offset, span) if i % secondary_divisions == 0: draw_tick_mark(tick_offset, primary_tick_width, primary_tick_height) else: draw_tick_mark(tick_offset, secondary_tick_width, secondary_tick_height) def draw_ticks_sigmoidal_normalized(primary_divisions, secondary_divisions, span): ticks = (primary_divisions * secondary_divisions) + 1 primary_tick_width = 0.005 primary_tick_height = 2 / 3.0 secondary_tick_width = primary_tick_width / 2.0 secondary_tick_height = primary_tick_height / 2.0 for i in range(0, ticks): tick_offset = i / float(ticks - 1) orig_offset = tick_offset tick_offset = xfer_func_sigmoidal_normalized(tick_offset, span) if i % secondary_divisions == 0: draw_tick_mark(tick_offset, primary_tick_width, primary_tick_height) else: draw_tick_mark(tick_offset, secondary_tick_width, secondary_tick_height) def draw_label(text="Volts", font_size=0.08, offset_x=0.0, offset_y=0.0): # use this to select a system font cr.select_font_face("Georgia", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) # use this to select a font file #face = create_cairo_font_face_for_file("./SynchroChronLCD.ttf") #cr.set_font_face(face) cr.set_font_size(font_size) cr.set_source_rgb(1, 1, 1) x_bearing, y_bearing, text_width, text_height = cr.text_extents(text)[:4] normalized_x = 0.0 - (text_width / 2.0) - x_bearing normalized_y = 0.0 - (text_height / 2.0) - y_bearing cr.move_to(normalized_x + offset_x, normalized_y + offset_y) cr.show_text(text) def draw_gauge_label(text="Volts", font_size=0.075, offset_x=0.0, offset_y=-0.025): draw_label(text, font_size, center_x + offset_x, center_y + offset_y) def draw_tick_label_linear(value, scale=5.0): text = str(value) value = value / scale angle = (value * (pi/2)) - (pi/4) arm_scale = 0.4375 offset_x = needle_hole_center_x + (sin(angle) * arm_scale) offset_y = needle_hole_center_y - (cos(angle) * arm_scale) font_size = 0.0375 draw_label(text, font_size, offset_x, offset_y) def draw_tick_label_sigmoidal(value, scale=5.0): text = str(value) value = value / scale value = xfer_func_sigmoidal(value) angle = (value * (pi/2)) - (pi/4) arm_scale = 0.4375 offset_x = needle_hole_center_x + (sin(angle) * arm_scale) offset_y = needle_hole_center_y - (cos(angle) * arm_scale) font_size = 0.0375 draw_label(text, font_size, offset_x, offset_y) def draw_tick_label_sigmoidal_normalized(value, scale=5.0): text = str(value) value = value / scale value = xfer_func_sigmoidal_normalized(value) angle = (value * (pi/2)) - (pi/4) arm_scale = 0.4375 offset_x = needle_hole_center_x + (sin(angle) * arm_scale) offset_y = needle_hole_center_y - (cos(angle) * arm_scale) font_size = 0.0375 draw_label(text, font_size, offset_x, offset_y) def draw_backlit_5volt_gauge(): # start with the basic mask draw_basic_mask(cr) # now for some tick marks. draw_ticks_linear(5, 10) # lets try some text draw_gauge_label() draw_tick_label_linear(0.0) draw_tick_label_linear(1.0) draw_tick_label_linear(2.0) draw_tick_label_linear(3.0) draw_tick_label_linear(4.0) draw_tick_label_linear(5.0) def draw_engine_coolant_gauge(): draw_basic_mask(cr) draw_ticks_sigmoidal_normalized(5, 10, 6.0) draw_gauge_label("Coolant") draw_tick_label_sigmoidal_normalized(0.0) draw_tick_label_sigmoidal_normalized(1.0) draw_tick_label_sigmoidal_normalized(2.0) draw_tick_label_sigmoidal_normalized(3.0) draw_tick_label_sigmoidal_normalized(4.0) draw_tick_label_sigmoidal_normalized(5.0) if __name__ == '__main__': # cairo init surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) cr = cairo.Context(surface) # normalize the canvas cr.scale(width/1.0, height/1.0) draw_backlit_5volt_gauge() #draw_engine_coolant_gauge() # dump to file surface.write_to_png("mask.png")