// merge float channels bool mergeOCPToImage::apply(const matrix<float>& c1, const matrix<float>& c2, const matrix<float>& c3, image& img) const { point p; // coordinates float r,g,b; // unnormed RGB channels float RG, BY, WB; // opponent colour channels if ((c1.size() != c2.size()) || (c1.size() != c3.size())) { setStatusString("sizes of channels do not match"); return false; } img.resize(c1.size(),rgbPixel(),false,false); for (p.y=0;p.y<img.rows();p.y++) { for (p.x=0;p.x<img.columns();p.x++) { RG = c1.at(p); BY = c2.at(p); WB = c3.at(p); b = BY*0.666666666667f; // r = WB + RG - b; g = WB - RG - b; b = WB + BY*1.3333333333333f; // truncate r,g and b if the value is not in intervall [0..1] // can happen due to rounding errors in split operation if (r<0.0f) { r=0.0f; } else if (r>1.0f) { r=1.0f; } if (g<0.0f) { g=0.0f; } else if (g>1.0f) { g=1.0f; } if (b<0.0f) { b=0.0f; } else if (b>1.0f) { b=1.0f; } img.at(p).set(static_cast<ubyte>(255.0f*r), static_cast<ubyte>(255.0f*g), static_cast<ubyte>(255.0f*b), 0); } } return true; };
// split image into 8-bit channels // N.B.: when casting the transformation result to unsigned shorts // (8-bit channel), major rounding errors will occur. // As a result, the merging operation might // produce negative values or values > 1, which are truncated subsequently. // When accurate X, Y and Z channels are required, prefer float channels! bool splitImageToxyY::apply(const image& img, channel8& c1, channel8& c2, channel8& c3) const { point p; // coordinates rgbPixel pix; // single Pixel Element in RGB-values... float Y; // channels float X, XYZ; // help variables // make the channels size of source image... c1.resize(img.rows(),img.columns(),0,false,false); c2.resize(img.rows(),img.columns(),0,false,false); c3.resize(img.rows(),img.columns(),0,false,false); for (p.y=0;p.y<img.rows();p.y++) for (p.x=0;p.x<img.columns();p.x++) { // take pixel at position p pix = img.at(p); // see Gonzales & Woods for explanation of magic numbers X = (((float)(pix.getRed())) *0.412453f + ((float)(pix.getGreen())) *0.357580f + ((float)(pix.getBlue())) *0.180423f)/255.0f; // x Y = (((float)(pix.getRed())) *0.212671f + ((float)(pix.getGreen())) *0.715160f + ((float)(pix.getBlue())) *0.072169f)/255.0f; // y XYZ = (((float)(pix.getRed())) *0.644458f + ((float)(pix.getGreen())) *1.191933f + ((float)(pix.getBlue())) *1.202819f)/255.0f; // Y if (XYZ>0.0f) { c1.at(p) = (ubyte)(X/XYZ*255.0f); // x c2.at(p) = (ubyte)(Y/XYZ*255.0f); // y } else { c1.at(p) = 0; // x c2.at(p) = 0; // y } c3.at(p) = (ubyte)(Y*255.0f); // Y } // loop return true; }
bool brightRGB::getAverage(const image& img,dvector& dest) const{ const rgbPixel transColor = getParameters().transColor; dvector avg(3,0.0); image::const_iterator it = img.begin(); // check for empty image if (img.columns()==0 || img.rows()==0) { setStatusString("image empty"); dest.resize(0); return false; } if(getParameters().transparent) { int counter = 0; while(it != img.end()) { if(*it != transColor) { avg.at(0) += (*it).getRed(); avg.at(1) += (*it).getGreen(); avg.at(2) += (*it).getBlue(); ++counter; } it++; } // check for completely transparent image if (counter==0) { setStatusString("only transparent pixels"); dest.resize(0); return false; } avg.divide(counter); } else { // no transparent color while(it != img.end()) { avg.at(0) += (*it).getRed(); avg.at(1) += (*it).getGreen(); avg.at(2) += (*it).getBlue(); it++; } avg.divide(img.columns()*img.rows()); } // values between 0 and 1 dest.divide(avg, 255.); return true; };
/* * create XImage */ void fastViewer::createImage(const image& img) { int screen; display_info_s& di = display_info; XWindowAttributes win_attributes; bool resizeWin = false; if ((di.height != img.rows()) || (di.width != img.columns())) { resizeWin = true; } di.height = img.rows(); di.width = img.columns(); if (di.win == 0) { createWindow(); } if (resizeWin) { XResizeWindow(di.display,di.win,di.width,di.height); } screen = DefaultScreen(di.display); XGetWindowAttributes(di.display, di.win, &win_attributes); di.depth = win_attributes.depth; // TODO: maybe screen->root_depth is more precise than win_attr.depth // We should try it. // check if the X-Server has a 32 bit interface if (di.depth < 24) { throw exception("Error: Fast Viewer works only with 32 bit depth!"); } if (useShareMemory) { // // Shared Memory Setup // di.shmimage = XShmCreateImage(di.display, DefaultVisual(di.display, screen), di.depth, ZPixmap, NULL, &shminfo, di.width, di.height); if(isNull(di.shmimage)) { throw exception("fastViewer::shmimage == NULL:"); } int sharedMemSize = di.shmimage->bytes_per_line * di.shmimage->height; shminfo.shmid = shmget(IPC_PRIVATE, sharedMemSize, IPC_CREAT | 0777); if(shminfo.shmid < 0) { std::string str; str = "fastViewer::shmget failed:"; str += strerror(errno); throw exception(str); } shminfo.shmaddr = (char *) shmat(shminfo.shmid, (void *) 0, 0); if (shminfo.shmaddr == 0) { std::string str; str = "fastViewer::shmmat failed:"; str += std::strerror(errno); throw exception(str); } di.shmimage->data = shminfo.shmaddr; XShmAttach(di.display, &shminfo); data.useExternData(di.height,di.width,(rgbPixel*)di.shmimage->data); data.fill(img); } else { // // without shared memory // const int blockSize = img.rows()*img.columns()*4; remoteData = new char[blockSize]; data.useExternData(di.height,di.width,(rgbPixel*)remoteData); data.fill(img); di.shmimage = XCreateImage(di.display, DefaultVisual(di.display, screen), di.depth, ZPixmap, 0, remoteData, di.width, di.height,8,0); if(isNull(di.shmimage)) { throw exception("fastViewer::shmimage == NULL:"); } } }
bool brightRGB::getMedian(const image& img,dvector& dest) const{ // image empty? if (img.empty()) { setStatusString("image empty"); dest.resize(0); return false; } const rgbPixel transColor = getParameters().transColor; dest.resize(3); ivector hist0(256,0); ivector hist1(256,0); ivector hist2(256,0); image::const_iterator it = img.begin(); if(getParameters().transparent) { while(it != img.end()) { if(*it != transColor) { ++hist0.at((*it).getRed()); ++hist1.at((*it).getGreen()); ++hist2.at((*it).getBlue()); } it++; } const int counterHalf = hist0.sumOfElements()/2; // check for complete image transparent if (counterHalf==0) { setStatusString("only transparent pixels"); dest.resize(0); return false; } int i,s; i=-1,s=0; while(++i<256 && s<counterHalf) { s += hist0.at(i); } dest.at(0) = i-1; i=-1,s=0; while(++i<256 && s<counterHalf) { s += hist1.at(i); } dest.at(1) = i-1; i=-1,s=0; while(++i<256 && s<counterHalf) { s += hist2.at(i); } dest.at(2) = i-1; } else { // no transparent color while(it != img.end()) { ++hist0.at((*it).getRed()); ++hist1.at((*it).getGreen()); ++hist2.at((*it).getBlue()); it++; } const int counterHalf = img.columns()*img.rows()/2; int i,s; i=-1,s=0; while(++i<256 && s<counterHalf) { s += hist0.at(i); } dest.at(0) = i-1; i=-1,s=0; while(++i<256 && s<counterHalf) { s += hist1.at(i); } dest.at(1) = i-1; i=-1,s=0; while(++i<256 && s<counterHalf) { s += hist2.at(i); } dest.at(2) = i-1; } // normalize to 0..1 dest.divide(255); return true; };
// Quantization takes place here! bool medianCut::performQuantization(const image& src, image& dest, channel8& mask, palette &thePalette) const { // parameters and const variables const parameters& param = getParameters(); const int imageRows=src.rows(); // number of rows in src const int imageCols=src.columns(); // number of columns in src // resize destination containers dest.resize(imageRows,imageCols,rgbPixel(),false,false); mask.resize(imageRows,imageCols,ubyte(),false,false); // Variables int row,col; // row, column counters int r,g,b; // red,green,blue ivector iVec(3); // int-vector std::list<boxInfo> theLeaves; // list of leaves (tree without root // and nodes) std::list<boxInfo>::iterator splitPos; // position to split std::list<boxInfo>::iterator iter; // iterator for theLeaves // create histogram with desired pre-quantization dimensions from src histogram theHist(3,param.preQuant); const float factor = param.preQuant/256.0f; for (row = 0 ; row < imageRows ; row++) { for (col = 0 ; col < imageCols ; col++) { r = static_cast<int>(src.at( row,col ).getRed() * factor); g = static_cast<int>(src.at( row,col ).getGreen() * factor); b = static_cast<int>(src.at( row,col ).getBlue() * factor); // insert point with quantized color dest.at(row,col).set((r*256+128)/param.preQuant, (g*256+128)/param.preQuant, (b*256+128)/param.preQuant,0); iVec[0] = r; iVec[1] = g; iVec[2] = b; theHist.put(iVec); } } // initialization of first box of list (the whole histogram) boxInfo theBox(rgbPixel(0,0,0), rgbPixel(param.preQuant-1, param.preQuant-1, param.preQuant-1)); computeBoxInfo(theHist,theBox); // return, if desired number of colors smaller than colors in // pre-quantized image if (theBox.colors < param.numberOfColors) { thePalette.resize(theBox.colors,rgbPixel(),false,false); // prepare palette int i = 0; for (r=0;r<param.preQuant;++r) { for (g=0;g<param.preQuant;++g) { for (b=0;b<param.preQuant;++b) { iVec[0] = r; iVec[1] = g; iVec[2] = b; if (theHist.at(iVec) > 0) { thePalette.at(i).set((r*256+128)/param.preQuant, (g*256+128)/param.preQuant, (b*256+128)/param.preQuant); } } } } // use the palette to generate the corresponding channel usePalette colorizer; colorizer.apply(dest,thePalette,mask); return true; } // Push first box into List theLeaves.push_back(theBox); // MAIN LOOP (do this until you have enough leaves (count), or no // splittable boxes (entries)) int count, entries=1; // auxiliary variables for the main loop for (count=1; (count<param.numberOfColors) && (entries!=0); count++) { // find box with largest number of entries from list entries = 0; for (iter = theLeaves.begin() ; iter != theLeaves.end() ; iter++) { if ( (*iter).colorFrequency > entries ) { // Avoid choosing single colors, i.e. unsplittable boxes if ( ((*iter).max.getRed() > (*iter).min.getRed()) || ((*iter).max.getGreen() > (*iter).min.getGreen()) || ((*iter).max.getBlue() > (*iter).min.getBlue()) ) { entries = (*iter).colorFrequency; splitPos = iter; } } } // A splittable box was found. // The iterator "splitPos" indicates its position in the List if (entries >0) { // Determine next axis to split (largest variance) and box dimensions int splitAxis; // split axis indicator if ( ((*splitPos).var[0] >= (*splitPos).var[1]) && ((*splitPos).var[0] >= (*splitPos).var[2]) ) { splitAxis = 0; // red axis } else if ( (*splitPos).var[1] >= (*splitPos).var[2] ) { splitAxis = 1; // green axis } else { splitAxis = 2; // blue axis } int rMax = ((*splitPos).max.getRed()); int rMin = ((*splitPos).min.getRed()); int gMax = ((*splitPos).max.getGreen()); int gMin = ((*splitPos).min.getGreen()); int bMax = ((*splitPos).max.getBlue()); int bMin = ((*splitPos).min.getBlue()); // pass through box along the axis to split bool found; // becomes true when split plane is found int nrOfCols=0; // counter: number of colors of box int prevNrOfCols=0; // forerunner of nrOfCols rgbPixel lower1; // lower pixel from box 1 rgbPixel upper1; // upper pixel from box 1 rgbPixel lower2; // lower pixel from box 2 rgbPixel upper2; // upper pixel from box 2 switch (splitAxis) { case 0: // red axis nrOfCols = 0; for (r = rMin , found = false ; (!found) && (r<=rMax) ; r++) { prevNrOfCols = nrOfCols; for (g = gMin ; g <= gMax ; g++) { for (b=bMin;b<=bMax;b++) { iVec[0] = r; iVec[1] = g; iVec[2] = b; if (theHist.at(iVec) > 0.0) { nrOfCols += static_cast<long int>(theHist.at(iVec)); } } } if ( nrOfCols >= (*splitPos).colorFrequency/2 ) { found=true; } } if (fabs(prevNrOfCols - static_cast<float>((*splitPos).colorFrequency)/2) < fabs(nrOfCols - static_cast<float>((*splitPos).colorFrequency)/2)) { r--; nrOfCols = prevNrOfCols; } // first box lower1.setRed(rMin); lower1.setGreen(gMin); lower1.setBlue(bMin); upper1.setRed(r-1); upper1.setGreen(gMax); upper1.setBlue(bMax); // second box lower2.setRed(r); lower2.setGreen(gMin); lower2.setBlue(bMin); upper2.setRed(rMax); upper2.setGreen(gMax); upper2.setBlue(bMax); break; case 1: // g axis nrOfCols = 0; for (g = gMin , found = false ; (!found) && (g<=gMax) ; g++) { prevNrOfCols = nrOfCols; for (r = rMin ; r <= rMax ; r++) { for (b = bMin ; b <= bMax ; b++) { iVec[0] = r; iVec[1] = g; iVec[2] = b; if (theHist.at(iVec) > 0.0) { nrOfCols += static_cast<long int>(theHist.at(iVec)); } } } if ( nrOfCols >= (*splitPos).colorFrequency/2 ) { found=true; } } if (fabs(prevNrOfCols - static_cast<float>((*splitPos).colorFrequency)/2) < fabs(nrOfCols - static_cast<float>((*splitPos).colorFrequency)/2)) { g--; nrOfCols = prevNrOfCols; } // first box lower1.setRed(rMin); lower1.setGreen(gMin); lower1.setBlue(bMin); upper1.setRed(rMax); upper1.setGreen(g-1); upper1.setBlue(bMax); // second box lower2.setRed(rMin); lower2.setGreen(g); lower2.setBlue(bMin); upper2.setRed(rMax); upper2.setGreen(gMax); upper2.setBlue(bMax); break; case 2: // b axis nrOfCols = 0; for (b = bMin , found = false ; (!found) && (b<=bMax) ; b++) { prevNrOfCols = nrOfCols; for (r = rMin ; r <= rMax ; r++) { for (g = gMin ; g <= gMax ; g++) { iVec[0] = r; iVec[1] = g; iVec[2] = b; if (theHist.at(iVec) > 0.0) { nrOfCols += static_cast<long int>(theHist.at(iVec)); } } } if ( nrOfCols >= (*splitPos).colorFrequency/2 ) { found=true; } } if (fabs(prevNrOfCols - static_cast<float>((*splitPos).colorFrequency)/2) < fabs(nrOfCols - static_cast<float>((*splitPos).colorFrequency)/2)) { b--; nrOfCols = prevNrOfCols; } // first box lower1.setRed(rMin); lower1.setGreen(gMin); lower1.setBlue(bMin); upper1.setRed(rMax); upper1.setGreen(gMax); upper1.setBlue(b-1); // second box lower2.setRed(rMin); lower2.setGreen(gMin); lower2.setBlue(b); upper2.setRed(rMax); upper2.setGreen(gMax); upper2.setBlue(bMax); break; default: break; } // end of switch // compute box info of new boxes and // append both at the end of list theBox.min = lower1; theBox.max = upper1; computeBoxInfo(theHist,theBox); theLeaves.push_back(theBox); theBox.min = lower2; theBox.max = upper2; computeBoxInfo(theHist,theBox); theLeaves.push_back(theBox); // delete splited box from list theLeaves.erase(splitPos); } } // end of for (MAIN LOOP) // compute block histogram and respective color palette thePalette.resize(theLeaves.size()); int i; for (iter = theLeaves.begin() , i=0 ; iter != theLeaves.end() ; iter++ , i++) { // misuse histogram as a look-up-table for (r = (*iter).min.getRed(); r <= (*iter).max.getRed(); r++) { for (g = (*iter).min.getGreen(); g <= (*iter).max.getGreen(); g++) { for (b = (*iter).min.getBlue(); b <= (*iter).max.getBlue(); b++) { iVec[0] = r; iVec[1] = g; iVec[2] = b; theHist.at(iVec) = i; // insert palette-index (refers to // color in palette) } } } // create palette r = (static_cast<int>((*iter).mean[0]*factor)*256+128)/param.preQuant; g = (static_cast<int>((*iter).mean[1]*factor)*256+128)/param.preQuant; b = (static_cast<int>((*iter).mean[2]*factor)*256+128)/param.preQuant; thePalette[i].set(r,g,b,0); // insert color } // create new image with palette and theHist dest.resize(imageRows,imageCols); mask.resize(imageRows,imageCols,0,false,true); // <= 256 colors? then also fill the mask if (thePalette.size() <= 256) { for (row = 0 ; row < imageRows ; row++) { for (col = 0 ; col < imageCols ; col++) { iVec[0] = static_cast<int>(src.at( row,col ).getRed() * factor); iVec[1] = static_cast<int>(src.at( row,col ).getGreen() * factor); iVec[2] = static_cast<int>(src.at( row,col ).getBlue() * factor); i = static_cast<int>(theHist.at( iVec )); dest.at(row,col) = thePalette[i];// insert point with quantized color mask.at(row,col) = i; // insert palette index of quantized color } } } else { for (row = 0 ; row < imageRows ; row++) { for (col = 0 ; col < imageCols ; col++) { iVec[0] = static_cast<int>(src.at( row,col ).getRed() * factor); iVec[1] = static_cast<int>(src.at( row,col ).getGreen() * factor); iVec[2] = static_cast<int>(src.at( row,col ).getBlue() * factor); i = static_cast<int>(theHist.at( iVec )); r = thePalette[i].getRed(); g = thePalette[i].getGreen(); b = thePalette[i].getBlue(); dest.at(row,col).set(r,g,b,0); // insert point with quantized color } } } return true; }
/* * shows an lti::mathObject * @param data the object to be shown. */ bool fastViewer::show(const image& img) { // Draw screen onto display if (img.rows()>0 && img.columns()>0) { if (data.size() == img.size()) { data.fill(img); } else { destroyImage(); createImage(img); } } else { setStatusString("empty image"); return false; } if (useShareMemory) { XShmPutImage(display_info.display, display_info.win, display_info.gc, display_info.shmimage, 0, 0, 0, 0, display_info.width, display_info.height, false); } else { XPutImage(display_info.display, display_info.win, display_info.gc, display_info.shmimage, 0, 0, 0, 0, display_info.width, display_info.height); } XSync(display_info.display,0); return true; }