Back to index

nux  3.0.0
CairoGraphics.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright 2010 Inalogic® Inc.
00003  *
00004  * This program is free software: you can redistribute it and/or modify it
00005  * under the terms of the GNU Lesser General Public License, as
00006  * published by the  Free Software Foundation; either version 2.1 or 3.0
00007  * of the License.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranties of
00011  * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
00012  * PURPOSE.  See the applicable version of the GNU Lesser General Public
00013  * License for more details.
00014  *
00015  * You should have received a copy of both the GNU Lesser General Public
00016  * License along with this program. If not, see <http://www.gnu.org/licenses/>
00017  *
00018  * Authored by: Jay Taoko <jaytaoko@inalogic.com>
00019  *
00020  */
00021 
00022 
00023 #include "NuxCore/NuxCore.h"
00024 #include "NuxCore/Rect.h"
00025 #include "BitmapFormats.h"
00026 #include "CairoGraphics.h"
00027 
00028 namespace nux
00029 {
00030 
00031   CairoGraphics::CairoGraphics(cairo_format_t format, int width, int height)
00032     :   _width(0)
00033     ,   _height(0)
00034   {
00035     nuxAssert(width >= 0);
00036     nuxAssert(height >= 0);
00037 
00038     _width = width;
00039     _height = height;
00040 
00041     if (_width <= 0)
00042       _width = 1;
00043 
00044     if (_height <= 0)
00045       _height = 1;
00046 
00047     _cairo_surface = cairo_image_surface_create(format, _width, _height);
00048     m_surface_format = format;
00049 
00050     _cr = cairo_create(_cairo_surface);
00051     if (cairo_status(_cr) == CAIRO_STATUS_NO_MEMORY)
00052     {
00053       // If memory cannot be allocated, a special cairo_t object will be returned
00054       // on which cairo_status() returns CAIRO_STATUS_NO_MEMORY.
00055       // You can use this object normally, but no drawing will be done. 
00056       nuxAssertMsg(0, "[CairoGraphics::GetContext] Cairo context error.");
00057     }
00058 
00059     _opacity = 1.0f;
00060     _zoom = 1.0;
00061   }
00062 
00063   CairoGraphics::~CairoGraphics()
00064   {
00065     if (_cr)
00066     {
00067       cairo_destroy(_cr);
00068     }
00069     cairo_surface_destroy(_cairo_surface);
00070   }
00071 
00072   cairo_t *CairoGraphics::GetContext()
00073   { 
00074     cairo_t *cr = cairo_create(_cairo_surface);
00075     if (cairo_status(cr) == CAIRO_STATUS_NO_MEMORY)
00076     {
00077       // If memory cannot be allocated, a special cairo_t object will be returned
00078       // on which cairo_status() returns CAIRO_STATUS_NO_MEMORY.
00079       // You can use this object normally, but no drawing will be done. 
00080       nuxAssertMsg(0, "[CairoGraphics::GetContext] Cairo context error.");
00081     }
00082     return cr;
00083   }
00084 
00085   cairo_t *CairoGraphics::GetInternalContext()
00086   { 
00087     return _cr;
00088   }
00089 
00090   cairo_surface_t* CairoGraphics::GetSurface()
00091   {
00092     return _cairo_surface;
00093   }
00094 
00095   NBitmapData *CairoGraphics::GetBitmap()
00096   {
00097     if ((_width <= 0) || (_height <= 0))
00098     {
00099       nuxDebugMsg("[CairoGraphics::GetBitmap] Invalid surface.");
00100     }
00101 
00102     NUX_RETURN_VALUE_IF_NULL(_width, 0);
00103     NUX_RETURN_VALUE_IF_NULL(_height, 0);
00104 
00105     BitmapFormat bitmap_format = BITFMT_UNKNOWN;
00106 
00107     if (m_surface_format == CAIRO_FORMAT_ARGB32)
00108     {
00109       // Each pixel is a 32-bit quantity, with alpha in the upper 8 bits,
00110       // then red, then green, then blue. The 32-bit quantities are stored native-endian.
00111       // Pre-multiplied alpha is used. (That is, 50% transparent red is 0x80800000, not 0x80ff0000.)
00112       bitmap_format = BITFMT_B8G8R8A8;
00113     }
00114 
00115     if (m_surface_format == CAIRO_FORMAT_RGB24)
00116     {
00117       // Each pixel is a 32-bit quantity, with the upper 8 bits unused.
00118       // Red, Green, and Blue are stored in the remaining 24 bits in that order.
00119       bitmap_format = BITFMT_B8G8R8A8;
00120     }
00121 
00122     if (m_surface_format == CAIRO_FORMAT_A8)
00123     {
00124       // Each pixel is a 8-bit quantity holding an alpha value.
00125       bitmap_format = BITFMT_A8;
00126     }
00127 
00128     if (m_surface_format == CAIRO_FORMAT_A1)
00129       bitmap_format = BITFMT_A8;
00130 
00131     NTextureData *bitmap_data = new NTextureData(bitmap_format, _width, _height, 1);
00132     unsigned char *ptr = cairo_image_surface_get_data(_cairo_surface);
00133     int stride = cairo_image_surface_get_stride(_cairo_surface);
00134 
00135     if (ptr == NULL || stride == 0)
00136     {
00137       // _cairo_surface is not a valid surface
00138       nuxError("[CairoGraphics::GetBitmap] Invalid surface");
00139       return bitmap_data; // just returns because we will segfault otherwise
00140     }
00141 
00142     if (m_surface_format == CAIRO_FORMAT_A1)
00143     {
00144       unsigned char *temp = new unsigned char[bitmap_data->GetSurface(0).GetPitch() ];
00145 
00146       for (int j = 0; j < _height; j++)
00147       {
00148         for (int i = 0; i < _width; i++)
00149         {
00150           // Get the byte
00151           int a = ptr[j * stride + i/8];
00152           // Get the position in the byte
00153           int b = (i - 8 * (i / 8));
00154           // Shift the byte and get the last bit
00155           int c = (a >> b) & 0x1;
00156           // If the last bit is set, put 1, otherwise put 0
00157           temp[i] = c ? 0xFF : 0x0;
00158         }
00159 
00160         Memcpy( bitmap_data->GetSurface(0).GetPtrRawData() + j * bitmap_data->GetSurface(0).GetPitch(),
00161                  (const void *) (&temp[0]),
00162                  _width);
00163       }
00164     }
00165     else
00166     {
00167       for (int j = 0; j < _height; j++)
00168       {
00169         Memcpy(bitmap_data->GetSurface(0).GetPtrRawData() + j * bitmap_data->GetSurface(0).GetPitch(),
00170                 (const void *) (&ptr[j * stride]),
00171                 _width * GPixelFormats[bitmap_format].NumComponents);
00172       }
00173     }
00174 
00175     return bitmap_data;
00176   }
00177 
00178   int CairoGraphics::GetWidth() const
00179   {
00180     return _width;
00181   }
00182 
00183   int CairoGraphics::GetHeight() const
00184   {
00185     return _height;
00186   }
00187 
00188   bool CairoGraphics::PushState()
00189   {
00190     nuxAssert(_cr);
00191     _opacity_stack.push(_opacity);
00192     cairo_save(_cr);
00193     return true;
00194   }
00195 
00196   bool CairoGraphics::PopState()
00197   {
00198     nuxAssert(_cr);
00199     if (_opacity_stack.empty())
00200     {
00201       return false;
00202     }
00203 
00204     _opacity = _opacity_stack.top();
00205     _opacity_stack.pop();
00206     cairo_restore(_cr);
00207     return true;
00208   }
00209 
00210   bool CairoGraphics::ClearCanvas()
00211   {
00212     // Clear the surface.
00213     nuxAssert(_cr);
00214     cairo_operator_t op = cairo_get_operator(_cr);
00215     cairo_set_operator(_cr, CAIRO_OPERATOR_CLEAR);
00216     cairo_paint(_cr);
00217     cairo_set_operator(_cr, op);
00218 
00219     // Set the clip region to an infinitely large shape containing the target.
00220     cairo_reset_clip(_cr);
00221 
00222     _opacity = 1.0f;
00223     _opacity_stack = std::stack<float>();
00224 
00225     cairo_restore(_cr);
00226     cairo_save(_cr);
00227 
00228     return true;
00229   }
00230 
00231   bool CairoGraphics::ClearRect(double x, double y, double w, double h)
00232   {
00233     nuxAssert(_cr);
00234     cairo_rectangle(_cr, x, y, w, h);
00235     cairo_operator_t op = cairo_get_operator(_cr);
00236     cairo_set_operator(_cr, CAIRO_OPERATOR_CLEAR);
00237     cairo_fill(_cr);
00238     cairo_set_operator(_cr, op);
00239     return true;
00240   }
00241 
00242   bool CairoGraphics::DrawLine(double x0, double y0, double x1, double y1,
00243                                double width, const Color &c)
00244   {
00245     nuxAssert(_cr);
00246     if (width < 0.0)
00247     {
00248       return false;
00249     }
00250 
00251     cairo_set_line_width(_cr, width);
00252     cairo_set_source_rgba(_cr, c.red, c.green, c.blue, _opacity);
00253     cairo_move_to(_cr, x0, y0);
00254     cairo_line_to(_cr, x1, y1);
00255     cairo_stroke(_cr);
00256 
00257     return true;
00258   }
00259 
00260   void CairoGraphics::TranslateCoordinates(double tx, double ty)
00261   {
00262     nuxAssert(_cr);
00263     cairo_translate(_cr, tx, ty);
00264   }
00265 
00266   bool CairoGraphics::DrawFilledRect(double x, double y, double w, double h,
00267                                      const Color &c)
00268   {
00269     nuxAssert(_cr);
00270     if (w <= 0.0 || h <= 0.0) {
00271       return false;
00272     }
00273 
00274     cairo_set_source_rgba(_cr, c.red, c.green, c.blue, _opacity);
00275     cairo_rectangle(_cr, x, y, w, h);
00276     cairo_fill(_cr);
00277     return true;
00278   }
00279 
00280   bool CairoGraphics::DrawCanvas(double x, double y, CairoGraphics *cg)
00281   {
00282     if (cg == 0) return false;
00283 
00284     cairo_surface_t *s = cg->GetSurface();
00285     double src_zoom = cg->_zoom;
00286     double inv_zoom = 1.0 / src_zoom;
00287 
00288     cairo_save(_cr);
00289 
00290     IntersectRectClipRegion(x, y, cg->GetWidth(), cg->GetHeight());
00291 
00292     cairo_scale(_cr, inv_zoom, inv_zoom);
00293     cairo_set_source_surface(_cr, s, x * src_zoom, y * src_zoom);
00294 
00295     cairo_pattern_set_extend(cairo_get_source(_cr), CAIRO_EXTEND_PAD);
00296 
00297     cairo_paint_with_alpha(_cr, _opacity);
00298     cairo_restore(_cr);
00299 
00300     return true;
00301   }
00302 
00303   static inline double
00304   _align(double val)
00305   {
00306     double fract = val - (int) val;
00307 
00308     if (fract != 0.5f)
00309       return(double) ((int) val + 0.5f);
00310     else
00311       return val;
00312   }
00313 
00314   bool
00315   CairoGraphics::DrawRoundedRectangle(cairo_t* cr,
00316                                        double   aspect,
00317                                        double   x,
00318                                        double   y,
00319                                        double   cornerRadius,
00320                                        double   width,
00321                                        double   height,
00322                                        bool     align)
00323   {
00324     double radius = cornerRadius / aspect;
00325 
00326     if (align)
00327     {
00328       // top-left, right of the corner
00329       cairo_move_to(cr, _align(x + radius), _align(y));
00330 
00331       // top-right, left of the corner
00332       cairo_line_to(cr, _align(x + width - radius), _align(y));
00333 
00334       // top-right, below the corner
00335       cairo_arc(cr,
00336                  _align(x + width - radius),
00337                  _align(y + radius),
00338                  radius,
00339                  -90.0f * G_PI / 180.0f,
00340                  0.0f * G_PI / 180.0f);
00341 
00342       // bottom-right, above the corner
00343       cairo_line_to(cr, _align(x + width), _align(y + height - radius));
00344 
00345       // bottom-right, left of the corner
00346       cairo_arc(cr,
00347                  _align(x + width - radius),
00348                  _align(y + height - radius),
00349                  radius,
00350                  0.0f * G_PI / 180.0f,
00351                  90.0f * G_PI / 180.0f);
00352 
00353       // bottom-left, right of the corner
00354       cairo_line_to(cr, _align(x + radius), _align(y + height));
00355 
00356       // bottom-left, above the corner
00357       cairo_arc(cr,
00358                  _align(x + radius),
00359                  _align(y + height - radius),
00360                  radius,
00361                  90.0f * G_PI / 180.0f,
00362                  180.0f * G_PI / 180.0f);
00363 
00364       // top-left, right of the corner
00365       cairo_arc(cr,
00366                  _align(x + radius),
00367                  _align(y + radius),
00368                  radius,
00369                  180.0f * G_PI / 180.0f,
00370                  270.0f * G_PI / 180.0f);
00371     }
00372     else
00373     {
00374       // top-left, right of the corner
00375       cairo_move_to(cr, x + radius, y);
00376 
00377       // top-right, left of the corner
00378       cairo_line_to(cr, x + width - radius, y);
00379 
00380       // top-right, below the corner
00381       cairo_arc(cr,
00382                  x + width - radius,
00383                  y + radius,
00384                  radius,
00385                  -90.0f * G_PI / 180.0f,
00386                  0.0f * G_PI / 180.0f);
00387 
00388       // bottom-right, above the corner
00389       cairo_line_to(cr, x + width, y + height - radius);
00390 
00391       // bottom-right, left of the corner
00392       cairo_arc(cr,
00393                  x + width - radius,
00394                  y + height - radius,
00395                  radius,
00396                  0.0f * G_PI / 180.0f,
00397                  90.0f * G_PI / 180.0f);
00398 
00399       // bottom-left, right of the corner
00400       cairo_line_to(cr, x + radius, y + height);
00401 
00402       // bottom-left, above the corner
00403       cairo_arc(cr,
00404                  x + radius,
00405                  y + height - radius,
00406                  radius,
00407                  90.0f * G_PI / 180.0f,
00408                  180.0f * G_PI / 180.0f);
00409 
00410       // top-left, right of the corner
00411       cairo_arc(cr,
00412                  x + radius,
00413                  y + radius,
00414                  radius,
00415                  180.0f * G_PI / 180.0f,
00416                  270.0f * G_PI / 180.0f);
00417     }
00418 
00419     return true;
00420   }
00421 
00422   static inline void _blurinner(guchar* pixel,
00423                                  gint*   zR,
00424                                  gint*   zG,
00425                                  gint*   zB,
00426                                  gint*   zA,
00427                                  gint    alpha,
00428                                  gint    aprec,
00429                                  gint    zprec)
00430   {
00431     gint R;
00432     gint G;
00433     gint B;
00434     guchar A;
00435 
00436     R = *pixel;
00437     G = *(pixel + 1);
00438     B = *(pixel + 2);
00439     A = *(pixel + 3);
00440 
00441     *zR += (alpha * ((R << zprec) - *zR)) >> aprec;
00442     *zG += (alpha * ((G << zprec) - *zG)) >> aprec;
00443     *zB += (alpha * ((B << zprec) - *zB)) >> aprec;
00444     *zA += (alpha * ((A << zprec) - *zA)) >> aprec;
00445 
00446     *pixel       = *zR >> zprec;
00447     *(pixel + 1) = *zG >> zprec;
00448     *(pixel + 2) = *zB >> zprec;
00449     *(pixel + 3) = *zA >> zprec;
00450   }
00451 
00452   static inline void _blurrow(guchar* pixels,
00453                                gint    width,
00454                                gint    height,
00455                                gint    channels,
00456                                gint    line,
00457                                gint    alpha,
00458                                gint    aprec,
00459                                gint    zprec)
00460   {
00461     gint    zR;
00462     gint    zG;
00463     gint    zB;
00464     gint    zA;
00465     gint    index;
00466     guchar* scanline;
00467 
00468     scanline = &(pixels[line * width * channels]);
00469 
00470     zR = *scanline << zprec;
00471     zG = *(scanline + 1) << zprec;
00472     zB = *(scanline + 2) << zprec;
00473     zA = *(scanline + 3) << zprec;
00474 
00475     for (index = 0; index < width; index ++)
00476       _blurinner(&scanline[index * channels], &zR, &zG, &zB, &zA, alpha, aprec,
00477       zprec);
00478 
00479     for (index = width - 2; index >= 0; index--)
00480       _blurinner(&scanline[index * channels], &zR, &zG, &zB, &zA, alpha, aprec,
00481       zprec);
00482   }
00483 
00484   static inline void _blurcol(guchar* pixels,
00485                                gint    width,
00486                                gint    height,
00487                                gint    channels,
00488                                gint    x,
00489                                gint    alpha,
00490                                gint    aprec,
00491                                gint    zprec)
00492   {
00493     gint zR;
00494     gint zG;
00495     gint zB;
00496     gint zA;
00497     gint index;
00498     guchar* ptr;
00499 
00500     ptr = pixels;
00501 
00502     ptr += x * channels;
00503 
00504     zR = *((guchar*) ptr    ) << zprec;
00505     zG = *((guchar*) ptr + 1) << zprec;
00506     zB = *((guchar*) ptr + 2) << zprec;
00507     zA = *((guchar*) ptr + 3) << zprec;
00508 
00509     for (index = width; index < (height - 1) * width; index += width)
00510       _blurinner((guchar*) &ptr[index * channels], &zR, &zG, &zB, &zA, alpha,
00511       aprec, zprec);
00512 
00513     for (index = (height - 2) * width; index >= 0; index -= width)
00514       _blurinner((guchar*) &ptr[index * channels], &zR, &zG, &zB, &zA, alpha,
00515       aprec, zprec);
00516   }
00517 
00518   //
00519   // pixels   image-data
00520   // width    image-width
00521   // height   image-height
00522   // channels image-channels
00523   //
00524   // in-place blur of image 'img' with kernel of approximate radius 'radius'
00525   //
00526   // blurs with two sided exponential impulse response
00527   //
00528   // aprec = precision of alpha parameter in fixed-point format 0.aprec
00529   //
00530   // zprec = precision of state parameters zR,zG,zB and zA in fp format 8.zprec
00531   //
00532   void _expblur(guchar* pixels,
00533                  gint    width,
00534                  gint    height,
00535                  gint    channels,
00536                  gint    radius,
00537                  gint    aprec,
00538                  gint    zprec)
00539   {
00540     gint alpha;
00541     gint row = 0;
00542     gint col = 0;
00543 
00544     if (radius < 1)
00545       return;
00546 
00547     // calculate the alpha such that 90% of 
00548     // the kernel is within the radius.
00549     // (Kernel extends to infinity)
00550     alpha = (gint) ((1 << aprec) * (1.0f - expf(-2.3f / (radius + 1.f))));
00551 
00552     for (; row < height; row++)
00553       _blurrow(pixels, width, height, channels, row, alpha, aprec, zprec);
00554 
00555     for (; col < width; col++)
00556       _blurcol(pixels, width, height, channels, col, alpha, aprec, zprec);
00557 
00558     return;
00559   }
00560 
00561   // if called like BlurSurface(radius) or BlurSurface(radius, NULL) it will
00562   // try to blur the image-surface of the internal cairo-context
00563   bool CairoGraphics::BlurSurface(unsigned int radius, cairo_surface_t* surf)
00564   {
00565     cairo_surface_t* surface;
00566     guchar*          pixels;
00567     guint            width;
00568     guint            height;
00569     cairo_format_t   format;
00570 
00571     if (surf)
00572       surface = surf;
00573     else
00574       surface = cairo_get_target(_cr);
00575 
00576     // don't do anything if we're not dealing with an image-surface
00577       if (cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_IMAGE)
00578       return false;
00579 
00580     // before we mess with the surface execute any pending drawing
00581     cairo_surface_flush(surface);
00582 
00583     pixels = cairo_image_surface_get_data(surface);
00584     width  = cairo_image_surface_get_width(surface);
00585     height = cairo_image_surface_get_height(surface);
00586     format = cairo_image_surface_get_format(surface);
00587 
00588     switch(format)
00589     {
00590       case CAIRO_FORMAT_ARGB32:
00591         _expblur(pixels, width, height, 4, radius, 16, 7);
00592       break;
00593 
00594       case CAIRO_FORMAT_RGB24:
00595         _expblur(pixels, width, height, 3, radius, 16, 7);
00596       break;
00597 
00598       case CAIRO_FORMAT_A8:
00599         _expblur(pixels, width, height, 1, radius, 16, 7);
00600       break;
00601 
00602       default :
00603         // do nothing
00604       break;
00605     }
00606 
00607     // inform cairo we altered the surfaces contents
00608     cairo_surface_mark_dirty(surface);
00609 
00610     return true;
00611   }
00612 
00613   bool CairoGraphics::IntersectRectClipRegion(double x, double y, double w, double h)
00614   {
00615     if (w <= 0.0 || h <= 0.0) {
00616       return false;
00617     }
00618 
00619     cairo_antialias_t pre = cairo_get_antialias(_cr);
00620     cairo_set_antialias(_cr, CAIRO_ANTIALIAS_NONE);
00621     cairo_rectangle(_cr, x, y, w, h);
00622     cairo_clip(_cr);
00623     cairo_set_antialias(_cr, pre);
00624     return true;
00625   }
00626 
00627   bool CairoGraphics::IntersectGeneralClipRegion(std::list<Rect> &region)
00628   {
00629     bool do_clip = false;
00630     cairo_antialias_t pre = cairo_get_antialias(_cr);
00631     cairo_set_antialias(_cr, CAIRO_ANTIALIAS_NONE);
00632     
00633     std::list<Rect>::iterator it;
00634     for (it = region.begin(); it != region.end(); it++)
00635     {
00636       Rect rect = (*it);
00637 
00638       if (!rect.IsNull())
00639       {
00640         cairo_rectangle(_cr, rect.x, rect.y, rect.width, rect.height);
00641         do_clip = true;
00642       }
00643     }
00644 
00645     if (do_clip)
00646     {
00647       cairo_clip(_cr);
00648     }
00649 
00650     cairo_set_antialias(_cr, pre);
00651     return true;
00652   }
00653 }
00654