File:  [parser3project] / parser3 / src / classes / image.C
Revision 1.35: download - view: text, annotated - select for diffs - revision graph
Fri Aug 31 10:18:08 2001 UTC (24 years, 10 months ago) by parser
Branches: MAIN
CVS tags: HEAD
image:font added space param
image:font changed charwidth alg, added kerning const[for now]

/** @file
	Parser: @b image parser class.

	Copyright(c) 2001 ArtLebedev Group(http://www.artlebedev.com)

	Author: Alexander Petrosyan <paf@design.ru>(http://design.ru/paf)

	$Id: image.C,v 1.35 2001/08/31 10:18:08 parser Exp $
*/
static const char *RCSId="$Id: image.C,v 1.35 2001/08/31 10:18:08 parser Exp $"; 

/*
	jpegsize: gets the width and height (in pixels) of a jpeg file
	Andrew Tong, werdna@ugcs.caltech.edu           February 14, 1995
	modified slightly by alex@ed.ac.uk
	and further still by rjray@uswest.com
	optimization and general re-write from tmetro@vl.com
	from perl by paf@design.ru
*/

#include "pa_config_includes.h"

#include "gif.h"

#include "pa_common.h"
#include "pa_request.h"
#include "pa_vfile.h"
#include "pa_vimage.h"

// defines

#define IMAGE_CLASS_NAME "image"

// class

class MImage : public Methoded {
public: // VStateless_class
	Value *create_new_value(Pool& pool) { return new(pool) VImage(pool); }

public:
	MImage(Pool& pool);

public: // Methoded
	bool used_directly() { return true; }

};

// helpers

/// simple buffered reader[from memory/file], used in _measure
class Measure_reader {
public:
	enum { READ_CHUNK_SIZE=0x400*10 };// 10K
	typedef size_t(*Func)(void *& buf, size_t limit, void *info);

	Measure_reader(Func afunc, void *ainfo) : 
		func(afunc), info(ainfo), 
		chunk(0), offset(0), size(0) {
	}

	size_t read(void *&buf, size_t limit) {
		if(offset+limit>size) // nothing left
			if(offset==0 || limit==1) { // only one-byte continuations allowed
				size=(*func)(chunk, READ_CHUNK_SIZE, info);
				offset=0;
			} else
				return 0;// as if EOF
		if(!size) // EOF
			return 0;
			
		// something left
		size_t read_size=min(offset+limit, size)-offset;
		buf=((unsigned char *)chunk)+offset;
		offset+=read_size;
		return read_size;
	}

private:
	Func func;
	void *info;

	void *chunk;
	size_t offset;
	size_t size;
};

/// GIF file header
struct GIF_Header {
	char       type[3];         // 'GIF'
	char       version[3];
	unsigned char       width[2];
	unsigned char       height[2];
	char       dif;
	char       fonColor;
	char       nulls;
};

/// JPEG record head
struct JPG_Segment_head {
	unsigned char marker;
	unsigned char code;
	unsigned char length[2];
};
/// JPEG frame header
struct JPG_Size_segment_body {
	char data;                    //< data precision of bits/sample
	unsigned char height[2];               //< image height
	unsigned char width[2];                //< image width
	char numComponents;           //< number of color components
};

//

inline short x_endian_to_int(unsigned char L, unsigned char H) {
	return(short)((H<<8) + L);
}

inline short big_endian_to_int(unsigned char b[2]) {
	return x_endian_to_int(b[1], b[0]);
}

inline short little_endian_to_int(unsigned char b[2]) {
	return x_endian_to_int(b[0], b[1]);
}

void measure_gif(Pool& pool, const String *origin_string, 
			 Measure_reader& reader, int& width, int& height) {

	void *buf;
	const int head_size=sizeof(GIF_Header);
	if(reader.read(buf, head_size)<head_size)
		PTHROW(0, 0, 
			origin_string, 
			"not GIF file - too small");
	GIF_Header *head=(GIF_Header *)buf;

	if(strncmp(head->type, "GIF", 3)!=0)
		PTHROW(0, 0, 
			origin_string, 
			"not GIF file - signature not found");	

	width=little_endian_to_int(head->width);
	height=little_endian_to_int(head->height);
}

void measure_jpeg(Pool& pool, const String *origin_string, 
			 Measure_reader& reader, int& width, int& height) {
	// JFIF format markers
	const unsigned char MARKER=0xFF;
	const unsigned char CODE_SIZE_FIRST=0xC0;
	const unsigned char CODE_SIZE_LAST=0xC3;

	void *buf;
	const size_t prefix_size=2;
	if(reader.read(buf, prefix_size)<prefix_size)
		PTHROW(0, 0, 
			origin_string, 
			"not JPEG file - too small");
	unsigned char *signature=(unsigned char *)buf;
	
	if(!(signature[0]==0xFF && signature[1]==0xD8)) 
		PTHROW(0, 0, 
			origin_string, 
			"not JPEG file - signature not found");

	bool found=false;
	while(true) {
		void *buf;
        // Extract the segment header.
		if(reader.read(buf, sizeof(JPG_Segment_head))<sizeof(JPG_Segment_head))
			break;		
		JPG_Segment_head *head=(JPG_Segment_head *)buf;

        // Verify that it's a valid segment.
		if(head->marker!=MARKER)
			break;

		if(head->code >= CODE_SIZE_FIRST && head->code  <= CODE_SIZE_LAST) {
            // Segments that contain size info
			if(reader.read(buf, sizeof(JPG_Size_segment_body))<sizeof(JPG_Size_segment_body))
				break;
			JPG_Size_segment_body *body=(JPG_Size_segment_body *)buf;
			
			width=big_endian_to_int(body->width);
			height=big_endian_to_int(body->height);
			found=true;
			break;
		} else {
            // Dummy read to skip over data
            size_t limit=big_endian_to_int(head->length) - 2;
			if(reader.read(buf, limit)<limit)
				break;
        }
	}

	if(!found)
		PTHROW(0, 0, 
			origin_string, 
			"broken JPEG file - size frame not found");
}

// measure center

void measure(Pool& pool, const String& file_name, 
			 Measure_reader& reader, int& width, int& height) {
	if(const char *cext=strrchr(file_name.cstr(), '.')) {
		cext++;
		if(strcasecmp(cext, "GIF")==0)
			measure_gif(pool, &file_name, reader, width, height);
		else if(strcasecmp(cext, "JPG")==0 || strcasecmp(cext, "JPEG")==0) 
			measure_jpeg(pool, &file_name, reader, width, height);
		else
			PTHROW(0, 0, 
				&file_name, 
				"unhandled image file name extension '%s'", cext);
	} else
		PTHROW(0, 0, 
			&file_name, 
			"can not determine image type - no file name extension");
}

/// used by image: _measure / read_mem
struct Read_mem_info {
	unsigned char *ptr;
	unsigned char *eof;
};
static size_t read_mem(void*& buf, size_t limit, void *info) {
	Read_mem_info& rmi=*static_cast<Read_mem_info *>(info);
	buf=rmi.ptr;
	size_t read_size=min(limit, (size_t)(rmi.eof-rmi.ptr));
	rmi.ptr+=read_size;
	return read_size;
}

/// used by image: _measure / read_disk
struct Read_disk_info {
	const String *file_spec;
	size_t offset;
};
static size_t read_disk(void*& buf, size_t limit, void *info) {
	Read_disk_info& rdi=*static_cast<Read_disk_info *>(info);
	Pool& pool=rdi.file_spec->pool();

	size_t read_size;
	file_read(pool, *rdi.file_spec, 
			   buf, read_size, 
			   false/*as_text*/, 
			   true/*fail_on_read_problem*/, 
			   rdi.offset, limit);

	rdi.offset+=read_size;
	return read_size;
}

// methods

static void _measure(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	Value& data=params->as_no_junction(0, "data must not be code");

	void *info;Measure_reader::Func read_func;
	Read_mem_info read_mem_info;
	Read_disk_info read_disk_info;
	const String *file_name;
	if(data.is_string()) {
		file_name=data.get_string();
		read_disk_info.file_spec=&r.absolute(*file_name);
		read_disk_info.offset=0;
		info=&read_disk_info;read_func=read_disk;
	} else {
		const VFile& vfile=*data.as_vfile();
		file_name=&static_cast<Value *>(vfile.fields().get(*name_name))->as_string();
		read_mem_info.ptr=(unsigned char *)vfile.value_ptr();
		read_mem_info.eof=read_mem_info.ptr+vfile.value_size();
		info=&read_mem_info;read_func=read_mem;
	}

	Measure_reader reader(read_func, info);
	int width, height;
	measure(pool, *file_name, reader, width, height);

	static_cast<VImage *>(r.self)->set(file_name, width, height);
}

/// used by image: _html / append_attrib_pair
struct Attrib_info {
	String *tag; ///< html tag being constructed
	Hash *skip; ///< tag attributes not to append to tag string [to skip]
};
static void append_attrib_pair(const Hash::Key& key, Hash::Val *val, void *info) {
	Attrib_info& ai=*static_cast<Attrib_info *>(info);

	if(ai.skip && ai.skip->get(key))
		return;

	Value& value=*static_cast<Value *>(val);
	// src="a.gif" width=123 ismap[=-1]
	*ai.tag << " " << key;
	if(value.is_string() || value.as_int()>=0)
		*ai.tag << "=\"" << value.as_string() << "\"";
}
static void _html(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	String tag(pool);
	tag << "<img";

	const Hash& fields=static_cast<VImage *>(r.self)->fields();
	Hash *attribs=0;

	if(params->size())
		if(attribs=params->get(0).get_hash()) {
			Attrib_info attrib_info={&tag, 0};
			attribs->for_each(append_attrib_pair, &attrib_info);
		} else
			PTHROW(0, 0, 
				&method_name, 
				"attributes must be must be hash");

	Attrib_info attrib_info={&tag, attribs};
	fields.for_each(append_attrib_pair, &attrib_info);
	tag << " />";
	r.write_pass_lang(tag);
}

static gdImage *load(Request& r, const String& method_name, 
					 const String& file_name){
	Pool& pool=r.pool();

	const char *file_name_cstr=r.absolute(file_name).cstr(String::UL_FILE_NAME);
	if(FILE *f=fopen(file_name_cstr, "rb")) {
		gdImage& image=*new(pool) gdImage(pool);
		bool ok=image.CreateFromGif(f);
		fclose(f);
		if(!ok)
			PTHROW(0, 0, 
				&file_name,
				"is not in GIF format");
		return &image;
	} else {
		PTHROW(0, 0, 
			&method_name, 
			"can not open '%s'", file_name_cstr);
		return 0;
	}
}


static void _load(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	Value& vfile_name=params->as_no_junction(0, "file name must not be code");
	const String& file_name=vfile_name.as_string();

	gdImage& image=*load(r, method_name, file_name);
	int width=image.SX();
	int height=image.SY();
	static_cast<VImage *>(r.self)->set(&file_name, width, height, &image);
}

static void _create(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	int width=r.process(params->get(0)).as_int();
	int height=r.process(params->get(1)).as_int();
	int bgcolor_value=0xffFFff;
	if(params->size()>2)
		bgcolor_value=r.process(params->get(2)).as_int();
	gdImage& image=*new(pool) gdImage(pool);
	image.Create(width, height);
	image.FilledRectangle(0, 0, width-1, height-1, image.Color(bgcolor_value));
	static_cast<VImage *>(r.self)->set(0, width, height, &image);
}

static void _gif(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	gdImage *image=static_cast<VImage *>(r.self)->image;
	if(!image)
		PTHROW(0, 0, 
			&method_name, 
			"does not contain an image");

	// could _ but don't thing it's wise to use $image.src for vfile.name

	String out(pool); image->Gif(out);
	
	VFile& vfile=*new(pool) VFile(pool);
	String& image_gif=*new(pool) String(pool, "image/gif");
	vfile.set(false/*not tainted*/, 
		out.cstr(String::UL_AS_IS), out.size(), 0, &image_gif);

	r.write_no_lang(vfile);
}

static void _line(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	gdImage *image=static_cast<VImage *>(r.self)->image;
	if(!image)
		PTHROW(0, 0, 
			&method_name, 
			"does not contain an image");

	image->Line(
		r.process(params->get(0)).as_int(), 
		r.process(params->get(1)).as_int(), 
		r.process(params->get(2)).as_int(), 
		r.process(params->get(3)).as_int(), 
		image->Color(r.process(params->get(4)).as_int()));
}

static void _fill(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	gdImage *image=static_cast<VImage *>(r.self)->image;
	if(!image)
		PTHROW(0, 0, 
			&method_name, 
			"does not contain an image");

	image->Fill(
		r.process(params->get(0)).as_int(), 
		r.process(params->get(1)).as_int(), 
		image->Color(r.process(params->get(2)).as_int()));
}

static void _rectangle(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	gdImage *image=static_cast<VImage *>(r.self)->image;
	if(!image)
		PTHROW(0, 0, 
			&method_name, 
			"does not contain an image");

	image->Rectangle(
		r.process(params->get(0)).as_int(), 
		r.process(params->get(1)).as_int(), 
		r.process(params->get(2)).as_int(), 
		r.process(params->get(3)).as_int(), 
		image->Color(r.process(params->get(4)).as_int()));
}

static void _bar(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	gdImage *image=static_cast<VImage *>(r.self)->image;
	if(!image)
		PTHROW(0, 0, 
			&method_name, 
			"does not contain an image");

	image->FilledRectangle(
		r.process(params->get(0)).as_int(), 
		r.process(params->get(1)).as_int(), 
		r.process(params->get(2)).as_int(), 
		r.process(params->get(3)).as_int(), 
		image->Color(r.process(params->get(4)).as_int()));
}

static void _replace(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	gdImage *image=static_cast<VImage *>(r.self)->image;
	if(!image)
		PTHROW(0, 0, 
			&method_name, 
			"does not contain an image");

	if((params->size()-2)%2) // I see your thoughts, but that's more readable
		PTHROW(0, 0, 
			&method_name, 
			"y coordinate missing");

	int n=(params->size()-2)/2;
	
	gdImage::Point *p=(gdImage::Point *)pool.malloc(sizeof(gdImage::Point)*n);
	for(int i=0; i<n; i++) {
		p[i].x=r.process(params->get(2+i*2+0)).as_int();
		p[i].y=r.process(params->get(2+i*2+1)).as_int();
	}
	image->FilledPolygonReplaceColor(p, n, 
		image->Color(r.process(params->get(0)).as_int()), // src color
		image->Color(r.process(params->get(1)).as_int()));// dest color
}

static void _polygon(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	gdImage *image=static_cast<VImage *>(r.self)->image;
	if(!image)
		PTHROW(0, 0, 
			&method_name, 
			"does not contain an image");

	if((params->size()-1)%2) // [I see..] see now?
		PTHROW(0, 0, 
			&method_name, 
			"y coordinate missing");

	int n=(params->size()-1)/2;
	
	gdImage::Point *p=(gdImage::Point *)pool.malloc(sizeof(gdImage::Point)*n);
	for(int i=0; i<n; i++) {
		p[i].x=r.process(params->get(1+i*2+0)).as_int();
		p[i].y=r.process(params->get(1+i*2+1)).as_int();
	}
	image->Polygon(p, n, 
		image->Color(r.process(params->get(0)).as_int()));
}

static void _polybar(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	gdImage *image=static_cast<VImage *>(r.self)->image;
	if(!image)
		PTHROW(0, 0, 
			&method_name, 
			"does not contain an image");

	if((params->size()-1)%2) // [I see..] see now?
		PTHROW(0, 0, 
			&method_name, 
			"y coordinate missing");

	int n=(params->size()-1)/2;
	
	gdImage::Point *p=(gdImage::Point *)pool.malloc(sizeof(gdImage::Point)*n);
	for(int i=0; i<n; i++) {
		p[i].x=r.process(params->get(1+i*2+0)).as_int();
		p[i].y=r.process(params->get(1+i*2+1)).as_int();
	}
	image->FilledPolygon(p, n, 
		image->Color(r.process(params->get(0)).as_int()));
}

// font

#define Y(y)(y+index*height+1)

/// simple gdImage-based font storage & text output 
class Font : public Pooled {
public:
	
	const static int kerning;
	int height;	    ///< Font heigth
	int monospace;	    ///< Default char width
	int spacebarspace; ///< spacebar width
	gdImage& ifont;
	const String& alphabet;
	
	Font(Pool& pool, 
		const String& aalphabet, 
		gdImage& aifont, int aheight, int amonospace, int aspacebarspace) : Pooled(pool), 
		alphabet(aalphabet), 
		height(aheight), monospace(amonospace),  spacebarspace(aspacebarspace),
		ifont(aifont) {
	}
	
	/* ******************************** char ********************************** */
	
	int index_of(char ch) {
		if(ch==' ') return -1;
		return alphabet.pos(&ch, 1);
	}
	
	int index_width(int index) {
		if(index<0)
			return spacebarspace;
		int tr=ifont.GetTransparent();
		for(int x=ifont.SX()-1; x>=0; x--) {
			for(int y=0; y<height-1; y++)
				if(ifont.GetPixel(x, Y(y))!=tr) 
					return x+1;
		}
		return 0;
	}
	
	void index_display(gdImage& image, int x, int y, int index){
		if(index>=0) 
			ifont.Copy(image, x, y, 0, Y(0), index_width(index), height-1);
	}
	
	/* ******************************** string ********************************** */
	/*
	int string_width(const char *cstr){
		int result=0;
		for(; *cstr; cstr++)
			result+=index_width(index_of(*cstr));
		return result;
	}
	*/
	
	void string_display(gdImage& image, int x, int y, const String& s){
		const char *cstr=s.cstr(String::UL_AS_IS);
		if(cstr) for(; *cstr; cstr++) {
			int index=index_of(*cstr);
			index_display(image, x, y, index);
			x+=kerning + (monospace ? monospace : index_width(index));
		}
	}
	
};
const int Font::kerning=1;

static void _font(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	Value& valphabet=params->as_no_junction(0, "alphabet must not be code");
	Value& file_name=params->as_no_junction(1, "file_name must not be code");
	int height=r.process(params->get(2)).as_int();
	int spacebar_width=r.process(params->get(3)).as_int();

	int monospace_width=params->size()>4?r.process(params->get(4)).as_int():0;

	static_cast<VImage *>(r.self)->font=new(pool) Font(pool, 
		valphabet.as_string(), 
		*load(r, method_name, file_name.as_string()), 
		height, monospace_width, spacebar_width);
}

static void _text(Request& r, const String& method_name, MethodParams *params) {
	Pool& pool=r.pool();

	int x=r.process(params->get(0)).as_int();
	int y=r.process(params->get(1)).as_int();
	const String& s=r.process(params->get(2)).as_string();

	VImage& vimage=*static_cast<VImage *>(r.self);
	if(vimage.image)
		if(vimage.font)
			vimage.font->string_display(*vimage.image, x, y, s);
		else
			PTHROW(0, 0,
				&method_name,
				"set the font first");
	else
		PTHROW(0, 0, 
			&method_name, 
			"does not contain an image");
}

// constructor

MImage::MImage(Pool& apool) : Methoded(apool) {
	set_name(*NEW String(pool(), IMAGE_CLASS_NAME));


	// ^image:measure[DATA]
	add_native_method("measure", Method::CT_DYNAMIC, _measure, 1, 1);

	// ^image.html[]
	// ^image.html[hash]
	add_native_method("html", Method::CT_DYNAMIC, _html, 0, 1);

	// ^image.load[background.gif]
	add_native_method("load", Method::CT_DYNAMIC, _load, 1, 1);

	// ^image.create[width;height] bgcolor=white
	// ^image.create[width;height;bgcolor]
	add_native_method("create", Method::CT_DYNAMIC, _create, 2, 3);

	// ^image.gif[]
	add_native_method("gif", Method::CT_DYNAMIC, _gif, 0, 0);

	// ^image.line(x0;y0;x1;y1;color)
	add_native_method("line", Method::CT_DYNAMIC, _line, 5, 5);

	// ^image.fill(x;y;color)
	add_native_method("fill", Method::CT_DYNAMIC, _fill, 3, 3);

	// ^image.rectangle(x0;y0;x1;y1;color)
	add_native_method("rectangle", Method::CT_DYNAMIC, _rectangle, 5, 5);

	// ^image.bar(x0;y0;x1;y1;color)
	add_native_method("bar", Method::CT_DYNAMIC, _bar, 5, 5);

	// ^image.replace(color-source;color-dest)(x;y)... point coord pairs
	add_native_method("replace", Method::CT_DYNAMIC, _replace, 2+3*2, 2+100*2);

	// ^image.polygon(color)(x;y)... point coord pairs
	add_native_method("polygon", Method::CT_DYNAMIC, _polygon, 1+3*2, 1+100*2);

	// ^image.polybar(color)(x;y)... point coord pairs
	add_native_method("polybar", Method::CT_DYNAMIC, _polybar, 1+3*2, 1+100*2);

    // ^image.font[alPHAbet;font-file-name.gif](height;spacebar_width)
    // ^image.font[alPHAbet;font-file-name.gif](height;spacebar_width;width)
	add_native_method("font", Method::CT_DYNAMIC, _font, 4, 5);

    // ^image.text(x;y)[text]
	add_native_method("text", Method::CT_DYNAMIC, _text, 3, 3);
	
}

// global variable

Methoded *image_class;

// creator

Methoded *MImage_create(Pool& pool) {
	return image_class=new(pool) MImage(pool);
}

E-mail: