Re: [casetta] 3-Color version of the function img_to_basic

[ Thread Index | Date Index | More lists.tuxfamily.org/casetta Archives ]


Hello,

2007/3/11, Fabien ANDRE <fabien.andre.g@xxxxxxxxxx>:
Hello,

I've corrected some bugs in the img_to_basic function and I was able to get this working with a 3-color picture.
However, the function has still lots of restrictions, I mean :
It works only with :
- 24bits picture WITHOUT ALPHA CANAL(RGB)
- Only 3 colors or less :  Black or any another color wich will be displayed as blue, Green (0,128,0) Orange (255,128,0).

For the restrictions, maybe your normalize function in pictures.py could "filter" a picture and get it working with my function, I have to check.

It's not the work of your function to check the size and things like the number of colors and alpha canal. All pictures used in casetta are from a casio format or have passed in the normalize function. This function remove alpha canal, resize the picture to 128*64 and convert colors. If your function is added in casetta_gtk, as all pictures are stored in the memory as in the calculator, there is no problem.

Maybe, It would be good to write a normalize function for black and white calculators such as Graph35+/100+...

The Graph 35+ (I don't know for the 100, but I think it's the same) work with exactly the same format than the Graph 65, but green and orange are not printed, and blue become black. So, for Casetta, it's just a question of view (I'll be possible to switch between mono and color mode in the picture editor of casetta_gtk, and a function will be added to convert orange and green in blue, to keep all colored pixel in a monochromatic picture).

See attachments for a new version of the function (pictures.py), a new script to test this function (test_img_to_basic.py, to get this stuff working, you have to put my new pictures.py in your casetta module path) and an image I succeded to get on my casio calculator (tux2.png).

I've tried your function, the result is better than the first function. I've a bug with the white color : when there is another colored pixel before the end of line, a blue line is printed in the space. I've fix this problem by adding a condition in the "end F-Line" block.
You can see as attachments your function (in pictures.py) with this modifications, and some others : I replaced the color constants by variables, because they can be changed (by users or developers), and added Cls and ViewWindow because I think your function may produce "ready to use" code.
I've also join my script to test your function, which use more the capacity of casetta (to save programs in any format, by example), and tux2bis.png, the result - a screencapture from my calculator - of the conversion of your tux by the modified function. You will see that the result is not perfect, but it's not far (green points on the top left corner are not on the screen of my calculator, maybe a transfer problem).

However, they are some problems with the beginning or the end of a line : on the beginning, (col = 0), you have condition with [col-1] ( =[-1]). This return the last pixel of the current line, which is not the expected behaviour.
And because your function to "save" the F-Line wait after the last pixel of the line, a pixel (or a colored line) which ends a line is not drawn.

For the end, just a presentation detail : could you write with 4-spaces tabs and less-than-80-characters line, as all the code in casetta ? (Yes, it's very unimportant detail ;-) )


I hope this will help !

Another time: a lot of thanks for your work, all contributions about casetta help !

--
Thesa ~ Florian Birée
e-mail : florian.biree@xxxxxxxxx
Messagerie Instantanée Jabber/XMPP/Google Talk : florian.biree@xxxxxxxxx
Site web et blog : http://filyb.info/
# -*- coding: utf-8 -*-
#
############################################################################
#
# (c) 2006 Florian Birée aka Thesa <florian.biree@xxxxxxxxxxx>
# (c) 2006 Achraf Cherti aka Asher256 <achrafcherti@xxxxxxxxx>
# function img_to_basic is (c) 2007 Xion345 aka Fabien ANDRE 
#                              <fabien.andre.g@xxxxxxxxxx>
#
# Website: <URL:http://casetta.tuxfamily.org>
#
# Version 0.3.0
#
# changelog:
#
# # 0.3.0 version:
#
#   o First release
#
############################################################################
#
# 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 2
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
# MA 02110-1301, USA.
#
# http://www.gnu.org/copyleft/gpl.html
#
############################################################################
"""Pictures manager for casetta"""

__author__ = "Florian Birée, Achraf Cherti"
__version__ = "0.3.0"
__copyright__ = "Copyright (c) 2006, Florian Birée, Achraf Cherti"
__license__ = "GPL"
__revision__ = "$Revision: $"
# $Source: $
__date__ = "$Date: $"

import Image
import os
import data as datacls

# Constants:
ORANGE = (255, 128, 0)
GREEN = (0, 128, 0)
BLUE = (0, 0, 128)
WHITE = (255, 255, 255)
CASIO_SIZE = (128, 64)
COLORS = (ORANGE, GREEN, BLUE, WHITE)
_COLOR_DIC = {'o' : ORANGE,
              'g' : GREEN,
              'b' : BLUE,
              'w' : WHITE}
DEFAULT_PALLET = ['b', 'g', 'w', 'o']

# Conversion functions
def normalize(image, colors=COLORS):
    """Convert an image object in a casio-like format.
    
    This will:
    resize image to 128x64
    convert color to 4-color mode
    return a data list.
    pal is a 4-color tuple.
    """
    image = image.resize(CASIO_SIZE, Image.ANTIALIAS)
    data = list(image.getdata())
    out = []
    # color conversion
    for pixel in data:
        red, green, blue = pixel[0], pixel[1], pixel[2]
        if red > green and red > blue:
            pixel = colors[0] # orange
        elif green > red and green > blue:
            pixel = colors[1] # green
        elif blue > green and blue > red:
            pixel = colors[2] # blue
        elif red == green and red > blue:
            pixel = colors[1] # green
        elif green == blue and green > red:
            pixel = colors[2] # blue
        elif red == blue and red > green:
            pixel = colors[2] # blue
        elif (red, green, blue) == (255, 255, 255):
            pixel = colors[3] # white
        elif (red, green, blue) == (0, 0, 0):
            pixel = colors[2] # blue
        out.append(pixel)
    return out

def data_to_sheets(data, wanted_pallet=DEFAULT_PALLET):
    """Make raw picture sheets from *normalized* data
    
    wanted_pallet is a list of color id as 'b', 'g', 'w' or 'o'
    Return a list of data sheets, sorted as wanted_pallet, with the
    color byte at 0."""
    sheets = []
    for color_id in wanted_pallet :
        color = _COLOR_DIC[color_id]
        rev_sheet = '0' * (128 * 64)
        for index in range(len(data)):
            if data[index] == color:
                line = index / 128
                col = (index % 128) / 8
                car = (index % 128) % 8
                pos = car + line * 8 + col * (8 * 64)
                rev_sheet = rev_sheet[:pos] + '1' + rev_sheet[pos + 1:]
        # reverse and compact the rev_sheet
        sheet = ''
        for index in range(0, len(data), 8):
            car = chr(int(rev_sheet[index : index + 8], 2))
            sheet = car + sheet
        sheets.append(chr(wanted_pallet.index(color_id) + 1) + sheet)
    return sheets

def sheets_to_raw(sheets, headers = None):
    """Join sheets to make raw data
    
    The function can add a sheet header form the headers list (one string by 
    sheet).
    """
    raw_img = ''
    for index in range(len(sheets)):
        if headers != None:
            raw_img += headers[index]
        raw_img += sheets[index]
    return raw_img

def data_to_raw(data, wanted_pallet=DEFAULT_PALLET, headers = None):
    """Work as sheets_to_raw(data_to_sheets(data)), with same arguments"""
    return sheets_to_raw(data_to_sheets(data, wanted_pallet), headers)

def raw_to_data(raw_picture, color_byte=0, pal=DEFAULT_PALLET, \
                colors=COLORS, background=WHITE):
    """Make image data from a raw picture
    
    colors is a 4-color tuple,
    color_byte is the index of the color_byte (last byte of a sheet header)
    pal is the ordoned list of the color corresponding to each sheet
    background is the default color of a pixel."""
    #make color dic:
    color_dic = { 'o' : colors[0],
                  'g' : colors[1],
                  'b' : colors[2],
                  'w' : colors[3]}
    #extract raw sheets from raw_picture
    raw_sheets = []
    sh_len = 0x400 + color_byte + 1
    for index in range(len(pal)):
        offset = index * sh_len
        raw_sheets.append(raw_picture[offset: offset + sh_len])
    #convert raw sheets to list data
    data = [background] * (CASIO_SIZE[0] * CASIO_SIZE[1])
    for raw_sheet in raw_sheets:
        color = ord(raw_sheet[color_byte]) - 1
        raw_sheet = raw_sheet[color_byte + 1:]
        for index in range(len(raw_sheet)):
            byte = bin(ord(raw_sheet[index]))
            col, line = (15 - ((index) / 64)) * 8, 63 - ((index) % 64)
            data_pos = line * 128 + (col )
            for bit_index in range(len(byte)):
                if byte[bit_index] == '1':
                    bit_pos = data_pos + bit_index 
                    data[bit_pos] = color_dic[pal[color]]
    return data

def raw_to_sheets(raw_picture, color_byte = 0, sheet_number = 4):
    """Split raw data into sheets"""
    raw_sheets = []
    sh_len = 0x400 + color_byte + 1
    for index in range(sheet_number):
        offset = index * sh_len
        raw_sheets.append(raw_picture[offset + color_byte: offset + sh_len])
    return raw_sheets

def change_sheets_pallet(sheets, old_pallet, new_pallet):
    """Resort sheets from old_pallet to new_pallet
    
    If a collor in new_pallet is not in old_pallet, a empty sheet
    will be made for this color.
    """
    sheet_dic = {}
    for index in range(len(old_pallet)):
        sheet_dic[old_pallet[index]] = sheets[index][1:]
    new_sheets = []
    for index in range(len(new_pallet)):
        new_sheets.append(chr(index + 1) + sheet_dic.get(new_pallet[index], \
                                                         '\x00' * (16 * 64)))
    return new_sheets
# File format management functions
def open_img(filename):
    """Open an image in any PIL-managed format, return a filedata"""
    # Build the FileData
    file_data = datacls.FileData()
    new_index = file_data.new_data()
    # Write metadata
    file_data[new_index].name = 'Picture1'
    file_data[new_index].dType = 'picture'
    file_data[new_index].pallet = DEFAULT_PALLET
    file_data[new_index].color_byte = 0
    # Write data
    img = Image.open(filename)
    file_data.data[new_index].raw_data = data_to_raw(normalize(img), \
                                                     DEFAULT_PALLET)
    return file_data

def save(filename, file_data, export=False):
    """Save a picture"""
    folder = os.path.dirname(filename)
    ext = os.path.splitext(filename)[1]
    # For each data
    if export:
        index_list = file_data.export
    else:
        index_list = range(len(file_data.data))
    for data in [file_data.data[index] for index in index_list]:
        if data.dType == 'picture' or data.dType == 'screencapture' :
            # make a filename from the name
            if len(index_list) == 1 and os.path.basename(filename) != '':
                # try to use filename as name
                data_filename = filename
            else:
                data_filename = os.path.join(folder, data.name + ext)
            # write data
            img = Image.new('RGB', CASIO_SIZE) 
            img.putdata(raw_to_data(data.raw_data, data.color_byte,
                                    data.pallet))
            img.save(data_filename)

# Misc functions
def bin(number, total_length = 8):
    """Convert a number in binary, filled with 0 to reach total_length."""
    out = ''
    while number != 0: 
        number, out = number >> 1, `number & 1` + out
    out = '0' * (total_length - len(out)) + out
    return out

# This function is a contribution of Xion345 aka Fabien ANDRE.
# Xion345, thank you!
def img_to_basic(imagen, colors = COLORS):
    """Convert a *normalized* image into a Casio Basic program
    
    This function only returns Horinzontal F-line. It may not be really
    optimised for size
    """
    orange, green, blue, white = colors
    program_header = "\Cls\n\ViewWindow 0,127,1,0,63,1\n"
    # Splits the BIG list given by normalize into a list of list
    # in order to have a table of 128 columns per 64 lines
    pictureTable = []
    numberLine = 0
    basicPicture = '' # A picture under the form of a basic program / Returned
                      # by the function
    line = 0
    while numberLine < 64:
        pictureTable.append(imagen[numberLine*128:((numberLine+1)*128)])
        numberLine += 1
    #print len(pictureTable)
    while line <= 63:
        #print "Ligne :", line
        col = 0
        currentColor = 0 # This var contains the previously saved color
        orig_x = 0
        orig_y = 0
        end_x = 0
        end_y = 0 # In fact this varible is totally unuseful because the table
                  # is browsed line per line so end_y = orig_y. It is just used
                  # to make my function clearer
        while col <= 127:
            #print "pixel ("+str(col)+","+str(line)+")"
            if pictureTable[line][col] != currentColor \
                    and pictureTable[line][col-1] == currentColor \
                    and currentColor in colors[:3] :
                # If you get a pixel which color is different from currentColor
                # and if before you have a colored pixel, that's because you
                # encountered the end of the F-line
                end_x -= 1
                if currentColor == orange:
                    #print "Orange"
                    basicPicture += "\\Orange "
                if currentColor == green:
                    #print "Vert"
                    basicPicture += "\\Green "
                # The origin of the reference mark is different on the casio
                # graph and on the picture. On casio origin is bottom left-hand
                # corner / On the picture and my table, it top left-hand corner
                # That's why it must do casio_line = 63-line
                casio_orig_y = 63 - orig_y 
                casio_end_y = 63 - end_y
                if end_x != orig_x:
                    basicPicture += "\\F-Line " + str(orig_x) + "," + \
                                    str(casio_orig_y) + "," + str(end_x) + \
                                    "," + str(casio_end_y) + "\n"
                else:
                    basicPicture += "\\PlotOn " + str(orig_x) + "," + \
                                    str(casio_orig_y) + "\n"
                currentColor = 0 # This var contains the previously saved color
                orig_x = 0
                orig_y = 0
                end_x = 0
                end_y = 0 
            if pictureTable[line][col] != white \
                    and pictureTable[line][col] != currentColor:
                # New F-line to add detected
                #print "Nouvelle F-ligne détectée en "+str(line)+","+str(col)
                currentColor = pictureTable[line][col]
                end_x = col
                orig_x = col
                end_y = line 
                orig_y = line
            if pictureTable[line][col] != white \
                    and pictureTable[line][col] == currentColor:
                # Continue the F-line
                end_x += 1
            col += 1
        line += 1
    return program_header + basicPicture
#! /usr/bin/env python
#-*- coding: utf-8 -*-

import casetta
import Image
import sys

# sys.argv[1] -> input picture file
# sys.argv[2] -> output casio file
#                (format managed by casetta following the extension)

if len(sys.argv) < 3:
    sys.stderr("Usage:\npic2prog.py image_file casio_program\n")
    sys.exit(2)

# Open the picture as a casio picture
casio_file = casetta.open_file(sys.argv[1])
picture_data = casio_file[0]

# Creat a new file_data, to receive the program
prgm_file = casetta.data.FileData()
prgm_data = prgm_file[prgm_file.new_data()]

# Fill metadata
prgm_data.name = picture_data.name
prgm_data.dType = 'program'

# Convert the picture
prgm_data.set_text(casetta.pictures.img_to_basic(\
                        casetta.pictures.raw_to_data(\
                            picture_data.raw_data, picture_data.color_byte)))

# Save the prgm_file
prgm_file.save(sys.argv[2])

Attachment: tux2bis.png
Description: PNG image



Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/