#! /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 from math import pi import math import cairo # 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_normalized_sigmoidal(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(primar_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_normalized_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) if __name__ == '__main__': # cairo init surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) cr = cairo.Context(surface) # set up a transform so that 1.0 is full width/height cr.scale(width/1.0, height/1.0) # start with the basic mask draw_basic_mask(cr) # now for some tick marks. #draw_ticks_linear(5, 10) draw_ticks_sigmoidal(5, 10, 6.0) # dump to file surface.write_to_png("mask.png")