3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 TextureSource::TextureSource(IrrlichtDevice *device):
25 m_main_atlas_image(NULL),
26 m_main_atlas_texture(NULL)
30 m_atlaspointer_cache_mutex.Init();
32 m_main_thread = get_current_thread_id();
34 // Add a NULL AtlasPointer as the first index, named ""
35 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
38 // Build main texture atlas
42 TextureSource::~TextureSource()
46 void TextureSource::processQueue()
51 if(m_get_texture_queue.size() > 0)
53 GetRequest<std::string, u32, u8, u8>
54 request = m_get_texture_queue.pop();
56 dstream<<"INFO: TextureSource::processQueue(): "
57 <<"got texture request with "
58 <<"name="<<request.key
61 GetResult<std::string, u32, u8, u8>
63 result.key = request.key;
64 result.callers = request.callers;
65 result.item = getTextureIdDirect(request.key);
67 request.dest->push_back(result);
71 u32 TextureSource::getTextureId(const std::string &name)
73 //dstream<<"INFO: getTextureId(): name="<<name<<std::endl;
77 See if texture already exists
79 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
80 core::map<std::string, u32>::Node *n;
81 n = m_name_to_id.find(name);
91 if(get_current_thread_id() == m_main_thread)
93 return getTextureIdDirect(name);
97 dstream<<"INFO: getTextureId(): Queued: name="<<name<<std::endl;
99 // We're gonna ask the result to be put into here
100 ResultQueue<std::string, u32, u8, u8> result_queue;
102 // Throw a request in
103 m_get_texture_queue.add(name, 0, 0, &result_queue);
105 dstream<<"INFO: Waiting for texture from main thread, name="
110 // Wait result for a second
111 GetResult<std::string, u32, u8, u8>
112 result = result_queue.pop_front(1000);
114 // Check that at least something worked OK
115 assert(result.key == name);
119 catch(ItemNotFoundException &e)
121 dstream<<"WARNING: Waiting for texture timed out."<<std::endl;
126 dstream<<"WARNING: getTextureId(): Failed"<<std::endl;
131 // Draw a progress bar on the image
132 void make_progressbar(float value, video::IImage *image);
135 Generate image based on a string like "stone.png" or "[crack0".
136 if baseimg is NULL, it is created. Otherwise stuff is made on it.
138 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
139 video::IVideoDriver* driver);
142 Generates an image from a full string like
143 "stone.png^mineral_coal.png^[crack0".
145 This is used by buildMainAtlas().
147 video::IImage* generate_image_from_scratch(std::string name,
148 video::IVideoDriver* driver);
151 This method generates all the textures
153 u32 TextureSource::getTextureIdDirect(const std::string &name)
155 dstream<<"INFO: getTextureIdDirect(): name="<<name<<std::endl;
157 // Empty name means texture 0
160 dstream<<"INFO: getTextureIdDirect(): name is empty"<<std::endl;
165 Calling only allowed from main thread
167 if(get_current_thread_id() != m_main_thread)
169 dstream<<"ERROR: TextureSource::getTextureIdDirect() "
170 "called not from main thread"<<std::endl;
175 See if texture already exists
178 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
180 core::map<std::string, u32>::Node *n;
181 n = m_name_to_id.find(name);
184 dstream<<"INFO: getTextureIdDirect(): name="<<name
185 <<" found in cache"<<std::endl;
186 return n->getValue();
190 dstream<<"INFO: getTextureIdDirect(): name="<<name
191 <<" NOT found in cache. Creating it."<<std::endl;
197 char separator = '^';
200 This is set to the id of the base image.
201 If left 0, there is no base image and a completely new image
204 u32 base_image_id = 0;
206 // Find last meta separator in name
207 s32 last_separator_position = -1;
208 for(s32 i=name.size()-1; i>=0; i--)
210 if(name[i] == separator)
212 last_separator_position = i;
217 If separator was found, construct the base name and make the
218 base image using a recursive call
220 std::string base_image_name;
221 if(last_separator_position != -1)
223 // Construct base name
224 base_image_name = name.substr(0, last_separator_position);
225 dstream<<"INFO: getTextureIdDirect(): Calling itself recursively"
226 " to get base image, name="<<base_image_name<<std::endl;
227 base_image_id = getTextureIdDirect(base_image_name);
230 dstream<<"base_image_id="<<base_image_id<<std::endl;
232 video::IVideoDriver* driver = m_device->getVideoDriver();
235 video::ITexture *t = NULL;
238 An image will be built from files and then converted into a texture.
240 video::IImage *baseimg = NULL;
242 // If a base image was found, copy it to baseimg
243 if(base_image_id != 0)
245 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
247 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
249 video::IImage *image = ap.atlas_img;
253 dstream<<"WARNING: getTextureIdDirect(): NULL image in "
254 <<"cache: \""<<base_image_name<<"\""
259 core::dimension2d<u32> dim = ap.intsize;
261 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
263 core::position2d<s32> pos_to(0,0);
264 core::position2d<s32> pos_from = ap.intpos;
268 v2s32(0,0), // position in target
269 core::rect<s32>(pos_from, dim) // from
272 dstream<<"INFO: getTextureIdDirect(): Loaded \""
273 <<base_image_name<<"\" from image cache"
279 Parse out the last part of the name of the image and act
283 std::string last_part_of_name = name.substr(last_separator_position+1);
284 dstream<<"last_part_of_name="<<last_part_of_name<<std::endl;
286 // Generate image according to part of name
287 if(generate_image(last_part_of_name, baseimg, driver) == false)
289 dstream<<"INFO: getTextureIdDirect(): "
290 "failed to generate \""<<last_part_of_name<<"\""
294 // If no resulting image, print a warning
297 dstream<<"WARNING: getTextureIdDirect(): baseimg is NULL (attempted to"
298 " create texture \""<<name<<"\""<<std::endl;
303 // Create texture from resulting image
304 t = driver->addTexture(name.c_str(), baseimg);
308 Add texture to caches (add NULL textures too)
311 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
313 u32 id = m_atlaspointer_cache.size();
319 core::dimension2d<u32> baseimg_dim(0,0);
321 baseimg_dim = baseimg->getDimension();
322 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
323 m_atlaspointer_cache.push_back(nap);
324 m_name_to_id.insert(name, id);
326 dstream<<"INFO: getTextureIdDirect(): name="<<name
327 <<": succesfully returning id="<<id<<std::endl;
332 std::string TextureSource::getTextureName(u32 id)
334 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
336 if(id >= m_atlaspointer_cache.size())
338 dstream<<"WARNING: TextureSource::getTextureName(): id="<<id
339 <<" >= m_atlaspointer_cache.size()="
340 <<m_atlaspointer_cache.size()<<std::endl;
344 return m_atlaspointer_cache[id].name;
348 AtlasPointer TextureSource::getTexture(u32 id)
350 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
352 if(id >= m_atlaspointer_cache.size())
353 return AtlasPointer(0, NULL);
355 return m_atlaspointer_cache[id].a;
358 void TextureSource::buildMainAtlas()
360 dstream<<"TextureSource::buildMainAtlas()"<<std::endl;
362 //return; // Disable (for testing)
364 video::IVideoDriver* driver = m_device->getVideoDriver();
367 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
369 // Create an image of the right size
370 core::dimension2d<u32> atlas_dim(1024,1024);
371 video::IImage *atlas_img =
372 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
376 A list of stuff to add. This should contain as much of the
377 stuff shown in game as possible, to minimize texture changes.
380 core::array<std::string> sourcelist;
382 sourcelist.push_back("stone.png");
383 sourcelist.push_back("mud.png");
384 sourcelist.push_back("sand.png");
385 sourcelist.push_back("grass.png");
386 sourcelist.push_back("grass_footsteps.png");
387 sourcelist.push_back("tree.png");
388 sourcelist.push_back("tree_top.png");
389 sourcelist.push_back("water.png");
390 sourcelist.push_back("leaves.png");
391 sourcelist.push_back("mud.png^grass_side.png");
393 sourcelist.push_back("stone.png^mineral_coal.png");
394 sourcelist.push_back("stone.png^mineral_iron.png");
395 sourcelist.push_back("mud.png^mineral_coal.png");
396 sourcelist.push_back("mud.png^mineral_iron.png");
397 sourcelist.push_back("sand.png^mineral_coal.png");
398 sourcelist.push_back("sand.png^mineral_iron.png");
400 // Padding to disallow texture bleeding
404 First pass: generate almost everything
406 core::position2d<s32> pos_in_atlas(0,0);
408 pos_in_atlas.Y += padding;
410 for(u32 i=0; i<sourcelist.size(); i++)
412 std::string name = sourcelist[i];
414 /*video::IImage *img = driver->createImageFromFile(
415 porting::getDataPath(name.c_str()).c_str());
419 core::dimension2d<u32> dim = img->getDimension();
420 // Make a copy with the right color format
421 video::IImage *img2 =
422 driver->createImage(video::ECF_A8R8G8B8, dim);
426 // Generate image by name
427 video::IImage *img2 = generate_image_from_scratch(name, driver);
430 dstream<<"WARNING: TextureSource::buildMainAtlas(): Couldn't generate texture atlas: Couldn't generate image \""<<name<<"\""<<std::endl;
434 core::dimension2d<u32> dim = img2->getDimension();
436 // Tile it a few times in the X direction
437 u16 xwise_tiling = 16;
438 for(u32 j=0; j<xwise_tiling; j++)
440 // Copy the copy to the atlas
441 img2->copyToWithAlpha(atlas_img,
442 pos_in_atlas + v2s32(j*dim.Width,0),
443 core::rect<s32>(v2s32(0,0), dim),
444 video::SColor(255,255,255,255),
448 // Copy the borders a few times to disallow texture bleeding
449 for(u32 side=0; side<2; side++) // top and bottom
450 for(s32 y0=0; y0<padding; y0++)
451 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
457 dst_y = y0 + pos_in_atlas.Y + dim.Height;
458 src_y = pos_in_atlas.Y + dim.Height - 1;
462 dst_y = -y0 + pos_in_atlas.Y-1;
463 src_y = pos_in_atlas.Y;
465 s32 x = x0 + pos_in_atlas.X * dim.Width;
466 video::SColor c = atlas_img->getPixel(x, src_y);
467 atlas_img->setPixel(x,dst_y,c);
473 Add texture to caches
477 u32 id = m_atlaspointer_cache.size();
479 // Create AtlasPointer
481 ap.atlas = NULL; // Set on the second pass
482 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
483 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
484 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
485 (float)dim.Width/(float)atlas_dim.Height);
486 ap.tiled = xwise_tiling;
488 // Create SourceAtlasPointer and add to containers
489 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
490 m_atlaspointer_cache.push_back(nap);
491 m_name_to_id.insert(name, id);
493 // Increment position
494 pos_in_atlas.Y += dim.Height + padding * 2;
500 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
504 Second pass: set texture pointer in generated AtlasPointers
506 for(u32 i=0; i<sourcelist.size(); i++)
508 std::string name = sourcelist[i];
509 if(m_name_to_id.find(name) == NULL)
511 u32 id = m_name_to_id[name];
512 //dstream<<"id of name "<<name<<" is "<<id<<std::endl;
513 m_atlaspointer_cache[id].a.atlas = t;
517 Write image to file so that it can be inspected
519 /*driver->writeImageToFile(atlas_img,
520 porting::getDataPath("main_atlas.png").c_str());*/
523 video::IImage* generate_image_from_scratch(std::string name,
524 video::IVideoDriver* driver)
526 dstream<<"INFO: generate_image_from_scratch(): "
527 "name="<<name<<std::endl;
533 video::IImage *baseimg = NULL;
535 char separator = '^';
537 // Find last meta separator in name
538 s32 last_separator_position = -1;
539 for(s32 i=name.size()-1; i>=0; i--)
541 if(name[i] == separator)
543 last_separator_position = i;
548 /*dstream<<"INFO: generate_image_from_scratch(): "
549 <<"last_separator_position="<<last_separator_position
553 If separator was found, construct the base name and make the
554 base image using a recursive call
556 std::string base_image_name;
557 if(last_separator_position != -1)
559 // Construct base name
560 base_image_name = name.substr(0, last_separator_position);
561 dstream<<"INFO: generate_image_from_scratch(): Calling itself recursively"
562 " to get base image, name="<<base_image_name<<std::endl;
563 baseimg = generate_image_from_scratch(base_image_name, driver);
567 Parse out the last part of the name of the image and act
571 std::string last_part_of_name = name.substr(last_separator_position+1);
572 dstream<<"last_part_of_name="<<last_part_of_name<<std::endl;
574 // Generate image according to part of name
575 if(generate_image(last_part_of_name, baseimg, driver) == false)
577 dstream<<"INFO: generate_image_from_scratch(): "
578 "failed to generate \""<<last_part_of_name<<"\""
586 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
587 video::IVideoDriver* driver)
589 // Stuff starting with [ are special commands
590 if(part_of_name[0] != '[')
592 // A normal texture; load it from a file
593 std::string path = porting::getDataPath(part_of_name.c_str());
594 dstream<<"INFO: getTextureIdDirect(): Loading path \""<<path
597 video::IImage *image = driver->createImageFromFile(path.c_str());
601 dstream<<"WARNING: Could not load image \""<<part_of_name
602 <<"\" from path \""<<path<<"\""
603 <<" while building texture"<<std::endl;
607 dstream<<"WARNING: Creating a dummy"<<" image for \""
608 <<part_of_name<<"\""<<std::endl;
610 // Just create a dummy image
611 //core::dimension2d<u32> dim(2,2);
612 core::dimension2d<u32> dim(1,1);
613 image = driver->createImage(video::ECF_A8R8G8B8, dim);
615 /*image->setPixel(0,0, video::SColor(255,255,0,0));
616 image->setPixel(1,0, video::SColor(255,0,255,0));
617 image->setPixel(0,1, video::SColor(255,0,0,255));
618 image->setPixel(1,1, video::SColor(255,255,0,255));*/
619 image->setPixel(0,0, video::SColor(255,myrand()%256,
620 myrand()%256,myrand()%256));
621 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
622 myrand()%256,myrand()%256));
623 image->setPixel(0,1, video::SColor(255,myrand()%256,
624 myrand()%256,myrand()%256));
625 image->setPixel(1,1, video::SColor(255,myrand()%256,
626 myrand()%256,myrand()%256));*/
629 // If base image is NULL, load as base.
632 dstream<<"INFO: Setting "<<part_of_name<<" as base"<<std::endl;
634 Copy it this way to get an alpha channel.
635 Otherwise images with alpha cannot be blitted on
636 images that don't have alpha in the original file.
638 core::dimension2d<u32> dim = image->getDimension();
639 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
640 image->copyTo(baseimg);
643 // Else blit on base.
646 dstream<<"INFO: Blitting "<<part_of_name<<" on base"<<std::endl;
647 // Size of the copied area
648 core::dimension2d<u32> dim = image->getDimension();
649 //core::dimension2d<u32> dim(16,16);
650 // Position to copy the blitted to in the base image
651 core::position2d<s32> pos_to(0,0);
652 // Position to copy the blitted from in the blitted image
653 core::position2d<s32> pos_from(0,0);
655 image->copyToWithAlpha(baseimg, pos_to,
656 core::rect<s32>(pos_from, dim),
657 video::SColor(255,255,255,255),
665 // A special texture modification
667 dstream<<"INFO: getTextureIdDirect(): generating special "
668 <<"modification \""<<part_of_name<<"\""
672 This is the simplest of all; it just adds stuff to the
673 name so that a separate texture is created.
675 It is used to make textures for stuff that doesn't want
676 to implement getting the texture from a bigger texture
679 if(part_of_name == "[forcesingle")
684 Adds a cracking texture
686 else if(part_of_name.substr(0,6) == "[crack")
690 dstream<<"WARNING: getTextureIdDirect(): baseimg==NULL "
691 <<"for part_of_name="<<part_of_name
692 <<", cancelling."<<std::endl;
696 u16 progression = stoi(part_of_name.substr(6));
697 // Size of the base image
698 core::dimension2d<u32> dim_base = baseimg->getDimension();
699 // Crack will be drawn at this size
701 // Size of the crack image
702 core::dimension2d<u32> dim_crack(cracksize,cracksize);
703 // Position to copy the crack from in the crack image
704 core::position2d<s32> pos_other(0, 16 * progression);
706 video::IImage *crackimage = driver->createImageFromFile(
707 porting::getDataPath("crack.png").c_str());
711 /*crackimage->copyToWithAlpha(baseimg, v2s32(0,0),
712 core::rect<s32>(pos_other, dim_base),
713 video::SColor(255,255,255,255),
716 for(u32 y0=0; y0<dim_base.Height/dim_crack.Height; y0++)
717 for(u32 x0=0; x0<dim_base.Width/dim_crack.Width; x0++)
719 // Position to copy the crack to in the base image
720 core::position2d<s32> pos_base(x0*cracksize, y0*cracksize);
721 crackimage->copyToWithAlpha(baseimg, pos_base,
722 core::rect<s32>(pos_other, dim_crack),
723 video::SColor(255,255,255,255),
731 [combine:WxH:X,Y=filename:X,Y=filename2
732 Creates a bigger texture from an amount of smaller ones
734 else if(part_of_name.substr(0,8) == "[combine")
736 Strfnd sf(part_of_name);
738 u32 w0 = stoi(sf.next("x"));
739 u32 h0 = stoi(sf.next(":"));
740 dstream<<"INFO: combined w="<<w0<<" h="<<h0<<std::endl;
741 core::dimension2d<u32> dim(w0,h0);
742 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
743 while(sf.atend() == false)
745 u32 x = stoi(sf.next(","));
746 u32 y = stoi(sf.next("="));
747 std::string filename = sf.next(":");
748 dstream<<"INFO: Adding \""<<filename
749 <<"\" to combined ("<<x<<","<<y<<")"
751 video::IImage *img = driver->createImageFromFile(
752 porting::getDataPath(filename.c_str()).c_str());
755 core::dimension2d<u32> dim = img->getDimension();
756 dstream<<"INFO: Size "<<dim.Width
757 <<"x"<<dim.Height<<std::endl;
758 core::position2d<s32> pos_base(x, y);
759 video::IImage *img2 =
760 driver->createImage(video::ECF_A8R8G8B8, dim);
763 img2->copyToWithAlpha(baseimg, pos_base,
764 core::rect<s32>(v2s32(0,0), dim),
765 video::SColor(255,255,255,255),
771 dstream<<"WARNING: img==NULL"<<std::endl;
777 Adds a progress bar, 0.0 <= N <= 1.0
779 else if(part_of_name.substr(0,12) == "[progressbar")
783 dstream<<"WARNING: getTextureIdDirect(): baseimg==NULL "
784 <<"for part_of_name="<<part_of_name
785 <<", cancelling."<<std::endl;
789 float value = stof(part_of_name.substr(12));
790 make_progressbar(value, baseimg);
793 "[noalpha:filename.png"
794 Use an image without it's alpha channel.
795 Used for the leaves texture when in old leaves mode, so
796 that the transparent parts don't look completely black
797 when simple alpha channel is used for rendering.
799 else if(part_of_name.substr(0,8) == "[noalpha")
803 dstream<<"WARNING: getTextureIdDirect(): baseimg!=NULL "
804 <<"for part_of_name="<<part_of_name
805 <<", cancelling."<<std::endl;
809 std::string filename = part_of_name.substr(9);
811 std::string path = porting::getDataPath(filename.c_str());
813 dstream<<"INFO: getTextureIdDirect(): Loading path \""<<path
816 video::IImage *image = driver->createImageFromFile(path.c_str());
820 dstream<<"WARNING: getTextureIdDirect(): Loading path \""
821 <<path<<"\" failed"<<std::endl;
825 core::dimension2d<u32> dim = image->getDimension();
826 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
829 for(u32 y=0; y<dim.Height; y++)
830 for(u32 x=0; x<dim.Width; x++)
832 video::SColor c = image->getPixel(x,y);
834 image->setPixel(x,y,c);
837 image->copyTo(baseimg);
843 [inventorycube{topimage{leftimage{rightimage
844 In every subimage, replace ^ with &.
845 Create an "inventory cube".
846 NOTE: This should be used only on its own.
847 Example (a grass block (not actually used in game):
848 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
850 else if(part_of_name.substr(0,14) == "[inventorycube")
854 dstream<<"WARNING: getTextureIdDirect(): baseimg!=NULL "
855 <<"for part_of_name="<<part_of_name
856 <<", cancelling."<<std::endl;
860 // This is just a placeholder
862 str_replace_char(part_of_name, '&', '^');
863 Strfnd sf(part_of_name);
865 std::string imagename_top = sf.next("{");
866 std::string imagename_left = sf.next("{");
867 std::string imagename_right = sf.next("{");
869 baseimg = generate_image_from_scratch(
870 imagename_top, driver);
874 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
876 dstream<<"WARNING: getTextureIdDirect(): EVDF_RENDER_TO_TARGET"
877 " not supported"<<std::endl;
883 dstream<<"INFO: inventorycube w="<<w0<<" h="<<h0<<std::endl;
884 core::dimension2d<u32> dim(w0,h0);
886 //baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
888 video::IImage *img_top = generate_image_from_scratch(
889 imagename_top, driver);
890 video::IImage *img_left = generate_image_from_scratch(
891 imagename_left, driver);
892 video::IImage *img_right = generate_image_from_scratch(
893 imagename_right, driver);
895 // Render target texture
896 video::ITexture *rtt = NULL;
897 std::string rtt_name = part_of_name + "_RTT";
899 rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str());
914 dstream<<"WARNING: getTextureIdDirect(): Invalid "
915 " modification: \""<<part_of_name<<"\""<<std::endl;
922 void make_progressbar(float value, video::IImage *image)
927 core::dimension2d<u32> size = image->getDimension();
932 u32 barwidth = size.Width - barpad_x*2;
933 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
935 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
937 video::SColor active(255,255,0,0);
938 video::SColor inactive(255,0,0,0);
939 for(u32 x0=0; x0<barwidth; x0++)
946 u32 x = x0 + barpos.X;
947 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
949 image->setPixel(x,y, *c);