/** @file Parser: @b image parser class. Copyright(c) 2001 ArtLebedev Group(http://www.artlebedev.com) Author: Alexander Petrosyan (http://design.ru/paf) $Id: image.C,v 1.46 2001/09/26 10:32:25 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 #ifndef DOXYGEN class Measure_reader { public: enum { READ_CHUNK_SIZE=0x400*20 };// 20K 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; }; #endif /// 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)type, "GIF", 3)!=0) PTHROW(0, 0, origin_string, "not GIF file - wrong signature"); 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)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))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)(info); buf=rmi.ptr; size_t read_size=min(limit, (size_t)(rmi.eof-rmi.ptr)); rmi.ptr+=read_size; return read_size; } #ifndef DOXYGEN struct Read_disk_info { const String *file_spec; size_t offset; }; #endif static size_t read_disk(void*& buf, size_t limit, void *info) { Read_disk_info& rdi=*static_cast(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(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(r.self)->set(file_name, width, height); } #ifndef DOXYGEN struct Attrib_info { String *tag; ///< html tag being constructed Hash *skip; ///< tag attributes not to append to tag string [to skip] }; #endif static void append_attrib_pair(const Hash::Key& key, Hash::Val *val, void *info) { Attrib_info& ai=*static_cast(info); if(ai.skip && ai.skip->get(key)) return; Value& value=*static_cast(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 << "(r.self)->fields(); Hash *attribs=0; if(params->size()) { Value &vattribs=params->get(0); if(vattribs.is_defined()) // allow 'void' if(Hash *attribs=vattribs.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 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_SPEC); 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 ℑ } 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(); const String& file_name=params->as_string(0, "file name must not be code"); gdImage& image=*load(r, method_name, file_name); int width=image.SX(); int height=image.SY(); static_cast(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=params->as_int(0, r); int height=params->as_int(1, r); int bgcolor_value=0xffFFff; if(params->size()>2) bgcolor_value=params->as_int(2, r); gdImage& image=*new(pool) gdImage(pool); image.Create(width, height); image.FilledRectangle(0, 0, width-1, height-1, image.Color(bgcolor_value)); static_cast(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(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); Value *content_type=new(pool) VString(*new(pool) String(pool, "image/gif")); vfile.set(false/*not tainted*/, out.cstr(String::UL_AS_IS), out.size(), 0, content_type); r.write_no_lang(vfile); } static void _line(Request& r, const String& method_name, MethodParams *params) { Pool& pool=r.pool(); gdImage *image=static_cast(r.self)->image; if(!image) PTHROW(0, 0, &method_name, "does not contain an image"); image->Line( params->as_int(0, r), params->as_int(1, r), params->as_int(2, r), params->as_int(3, r), image->Color(params->as_int(4, r))); } static void _fill(Request& r, const String& method_name, MethodParams *params) { Pool& pool=r.pool(); gdImage *image=static_cast(r.self)->image; if(!image) PTHROW(0, 0, &method_name, "does not contain an image"); image->Fill( params->as_int(0, r), params->as_int(1, r), image->Color(params->as_int(2, r))); } static void _rectangle(Request& r, const String& method_name, MethodParams *params) { Pool& pool=r.pool(); gdImage *image=static_cast(r.self)->image; if(!image) PTHROW(0, 0, &method_name, "does not contain an image"); image->Rectangle( params->as_int(0, r), params->as_int(1, r), params->as_int(2, r), params->as_int(3, r), image->Color(params->as_int(4, r))); } static void _bar(Request& r, const String& method_name, MethodParams *params) { Pool& pool=r.pool(); gdImage *image=static_cast(r.self)->image; if(!image) PTHROW(0, 0, &method_name, "does not contain an image"); image->FilledRectangle( params->as_int(0, r), params->as_int(1, r), params->as_int(2, r), params->as_int(3, r), image->Color(params->as_int(4, r))); } #ifndef DOXYGEN static void add_point(Array::Item *value, void *info) { Array& row=*static_cast(value); gdImage::Point **p=static_cast(info); (**p).x=row.get_string(0)->as_int(); (**p).y=row.get_string(1)->as_int(); (*p)++; } #endif static void _replace(Request& r, const String& method_name, MethodParams *params) { Pool& pool=r.pool(); gdImage *image=static_cast(r.self)->image; if(!image) PTHROW(0, 0, &method_name, "does not contain an image"); Table *table=params->as_no_junction(2, "coordinates must not be code").get_table(); if(!table) PTHROW(0, 0, &method_name, "coordinates must be table"); gdImage::Point *all_p=(gdImage::Point *)pool.malloc(sizeof(gdImage::Point)*table->size()); gdImage::Point *add_p=all_p; table->for_each(add_point, &add_p); image->FilledPolygonReplaceColor(all_p, table->size(), image->Color(params->as_int(0, r)), // src color image->Color(params->as_int(1, r)));// dest color } static void _polyline(Request& r, const String& method_name, MethodParams *params) { Pool& pool=r.pool(); gdImage *image=static_cast(r.self)->image; if(!image) PTHROW(0, 0, &method_name, "does not contain an image"); Table *table=params->as_no_junction(1, "coordinates must not be code").get_table(); if(!table) PTHROW(0, 0, &method_name, "coordinates must be table"); gdImage::Point *all_p=(gdImage::Point *)pool.malloc(sizeof(gdImage::Point)*table->size()); gdImage::Point *add_p=all_p; table->for_each(add_point, &add_p); image->Polygon(all_p, table->size(), image->Color(params->as_int(0, r)), false/*not closed*/); } static void _polygon(Request& r, const String& method_name, MethodParams *params) { Pool& pool=r.pool(); gdImage *image=static_cast(r.self)->image; if(!image) PTHROW(0, 0, &method_name, "does not contain an image"); Table *table=params->as_no_junction(1, "coordinates must not be code").get_table(); if(!table) PTHROW(0, 0, &method_name, "coordinates must be table"); gdImage::Point *all_p=(gdImage::Point *)pool.malloc(sizeof(gdImage::Point)*table->size()); gdImage::Point *add_p=all_p; table->for_each(add_point, &add_p); image->Polygon(all_p, table->size(), image->Color(params->as_int(0, r))); } static void _polybar(Request& r, const String& method_name, MethodParams *params) { Pool& pool=r.pool(); gdImage *image=static_cast(r.self)->image; if(!image) PTHROW(0, 0, &method_name, "does not contain an image"); Table *table=params->as_no_junction(1, "coordinates must not be code").get_table(); if(!table) PTHROW(0, 0, &method_name, "coordinates must be table"); gdImage::Point *all_p=(gdImage::Point *)pool.malloc(sizeof(gdImage::Point)*table->size()); gdImage::Point *add_p=all_p; table->for_each(add_point, &add_p); image->FilledPolygon(all_p, table->size(), image->Color(params->as_int(0, r))); } // font #define Y(y)(y+index*height+1) /// simple gdImage-based font storage & text output class Font : public Pooled { public: const static int letter_spacing; 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=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+=letter_spacing + (monospace ? monospace : index_width(index)); } } }; const int Font::letter_spacing=1; static void _font(Request& r, const String& method_name, MethodParams *params) { Pool& pool=r.pool(); const String& alphabet=params->as_string(0, "alphabet must not be code"); gdImage& image=*load(r, method_name, params->as_string(1, "file_name must not be code")); int spacebar_width=params->as_int(2, r); int monospace_width; if(params->size()>3) { monospace_width=params->as_int(3, r); if(!monospace_width) monospace_width=image.SX(); } else monospace_width=0; if(!alphabet.size()) PTHROW(0, 0, &method_name, "alphabet must not be empty"); static_cast(r.self)->font=new(pool) Font(pool, alphabet, image, image.SY() / alphabet.size(), monospace_width, spacebar_width); } static void _text(Request& r, const String& method_name, MethodParams *params) { Pool& pool=r.pool(); int x=params->as_int(0, r); int y=params->as_int(1, r); const String& s=params->as_string(2, "text must not be code"); VImage& vimage=*static_cast(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)[table x:y] add_native_method("replace", Method::CT_DYNAMIC, _replace, 3, 3); // ^image.polyline(color)[table x:y] add_native_method("polyline", Method::CT_DYNAMIC, _polyline, 2, 2); // ^image.polygon(color)[table x:y] add_native_method("polygon", Method::CT_DYNAMIC, _polygon, 2, 2); // ^image.polybar(color)[table x:y] add_native_method("polybar", Method::CT_DYNAMIC, _polybar, 2, 2); // ^image.font[alPHAbet;font-file-name.gif](spacebar_width) // ^image.font[alPHAbet;font-file-name.gif](spacebar_width;width) add_native_method("font", Method::CT_DYNAMIC, _font, 3, 4); // ^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); }