image_page.cpp

Go to the documentation of this file.
00001 /*
00002  * File Name: image_page.cpp
00003  */
00004 
00005 /*
00006  * This file is part of uds-plugin-images.
00007  *
00008  * uds-plugin-images is free software: you can redistribute it and/or modify
00009  * it under the terms of the GNU General Public License as published by
00010  * the Free Software Foundation, either version 2 of the License, or
00011  * (at your option) any later version.
00012  *
00013  * uds-plugin-images is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00016  * GNU General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU General Public License
00019  * along with this program. If not, see <http://www.gnu.org/licenses/>.
00020  */
00021 
00022 /**
00023  * Copyright (C) 2008 iRex Technologies B.V.
00024  * All rights reserved.
00025  */
00026 
00027 #include <string.h>
00028 #ifdef WIN32
00029 #include <windows.h>
00030 #else
00031 #include <sys/sysinfo.h>
00032 #endif
00033 #include <time.h>
00034 
00035 #include <cassert>
00036 #include <gdk-pixbuf/gdk-pixbuf-transform.h>
00037 
00038 #include "plugin_config.h"
00039 
00040 #include "image_page.h"
00041 #include "image_dither.h"
00042 #include "utils.h"
00043 #include "log.h"
00044 
00045 namespace images
00046 {
00047 
00048 bool ImagePage::g_type_initialize;
00049 
00050 ImagePage::ImagePage(const std::string & p, 
00051                      const ImagePageAttrs & request)
00052 : path(p)
00053 , in_use(false)
00054 , timestamp(0)
00055 , loader(0)
00056 , valid(false)
00057 {
00058     LOGPRINTF("%s", p.c_str());
00059     
00060     attrs.original_width      = request.original_width;
00061     attrs.original_height     = request.original_height;
00062     attrs.desired_width       = request.desired_width;
00063     attrs.desired_height      = request.desired_height;
00064     attrs.desired_color_depth = request.desired_color_depth;
00065 
00066     attrs.zoom           = request.zoom;
00067     attrs.rotation       = request.rotation;
00068     attrs.final_rotation = request.final_rotation;
00069 
00070     display_width  = request.desired_width;
00071     display_height = request.desired_height;
00072 }
00073 
00074 ImagePage::~ImagePage(void)
00075 {
00076     LOGPRINTF("entry %s", path.c_str());
00077 
00078     destroy();
00079 }
00080 
00081 void ImagePage::init_type_system()
00082 {
00083     LOGPRINTF("entry");
00084 
00085     if (!g_type_initialize)
00086     {
00087         g_type_initialize = true;
00088         // Since UDS already calls g_type_init, no need do it again here.
00089         // Removing it because valgrind reports g_type_init causes memory leak.
00090         // This might avoid leaking in plugin side.
00091         // g_type_init();
00092     }
00093 }
00094 
00095 // Steps to render a image
00096 // 1. Load the image. 
00097 // 3. Dither the image.
00098 bool ImagePage::render(void)
00099 {
00100     LOGPRINTF("entry: anchor [%s]", anchor.c_str());
00101 
00102     if (!try_to_load_at_scale())
00103     {
00104         return false;
00105     }
00106 
00107     if (!update_attributes())
00108     {
00109         return false;
00110     }
00111 
00112     if (!try_to_dither())
00113     {
00114         return false;
00115     }
00116 
00117     if (!try_to_rotate_grey())
00118     {
00119         return false;
00120     }
00121 
00122     return true;
00123 }
00124 
00125 void ImagePage::calc_desired_dimension(ImagePageAttrs & request)
00126 {
00127     int w = request.original_width;
00128     int h = request.original_height;
00129     unsigned int display_w = request.desired_width;
00130     unsigned int display_h = request.desired_height;
00131     
00132     PluginRotationDegree autorotate = check_autorotate(w, h, display_w, display_h);
00133 
00134     // TODO: Correct for autorotation, if needed.
00135     int rotation = static_cast<int>(request.rotation);
00136     rotation = (rotation + autorotate) % Clockwise_Degrees_360;
00137     request.final_rotation = static_cast<PluginRotationDegree>(rotation);
00138     if (   rotation == Clockwise_Degrees_90
00139         || rotation == Clockwise_Degrees_270)
00140     {
00141         w = request.original_height;
00142         h = request.original_width;
00143         request.original_width = w;
00144         request.original_height = h;
00145     }
00146 
00147     // Calculate the desired width and height.
00148     if (request.zoom == PLUGIN_ZOOM_TO_PAGE ||
00149         request.zoom == PLUGIN_ZOOM_TO_CROP_BY_PAGE)
00150     {
00151         float ratio_width = static_cast<float>(request.desired_width) /
00152                              static_cast<float>(w);
00153 
00154         float ratio_height = static_cast<float>(request.desired_height) /
00155                               static_cast<float>(h);
00156 
00157         if (ratio_width > ratio_height)
00158         {
00159             request.desired_width = static_cast<int>(ratio_height * w);
00160         }
00161         else
00162         {
00163             request.desired_height = static_cast<int>(ratio_width * h);
00164         }
00165     }
00166     else if (request.zoom == PLUGIN_ZOOM_TO_WIDTH ||
00167              request.zoom == PLUGIN_ZOOM_TO_CROP_BY_WIDTH)
00168     {
00169         float ratio_width = static_cast<float>(request.desired_width) /
00170                              static_cast<float>(w);
00171         request.desired_height = static_cast<int>(h * ratio_width);
00172     }
00173     else if (request.zoom > 0)
00174     {
00175         request.desired_width = static_cast<int>(
00176             static_cast<float>(request.zoom * w) / static_cast<float>(100));
00177         request.desired_height = static_cast<int>(
00178             static_cast<float>(request.zoom * h) / static_cast<float>(100));
00179     }
00180 }
00181 
00182 bool ImagePage::try_to_load_at_scale(void)
00183 {
00184     // Check the image is already loaded or not. 
00185     if (loader)
00186     {
00187         return true;
00188     }
00189     
00190     // Calculate the desired image size and rotation.
00191     calc_desired_dimension(attrs);
00192     
00193     // Use gdk-pixbuf to load the image at size.
00194     gint w, h;
00195     if (attrs.final_rotation == Clockwise_Degrees_90 ||
00196         attrs.final_rotation == Clockwise_Degrees_270  )
00197     {
00198         w = attrs.desired_height;
00199         h = attrs.desired_width;
00200     }
00201     else
00202     {
00203         w = attrs.desired_width;
00204         h = attrs.desired_height;
00205     }
00206 
00207     GError *err = 0;
00208     loader = gdk_pixbuf_new_from_file_at_size(path.c_str(), w, h, &err);
00209     if (loader == 0)
00210     {
00211         ERRORPRINTF("load error at size [%d x %d] error [%s]", w, h, err ? err->message : "?");
00212         if (err) { g_error_free(err); err = 0; }
00213         return false;
00214     }
00215 
00216     // Update the attrs.
00217     attrs.original_color_depth = gdk_pixbuf_get_bits_per_sample(loader)
00218                                  * gdk_pixbuf_get_n_channels(loader);
00219     return true;
00220 }
00221 
00222 bool ImagePage::try_to_rotate_grey(void)
00223 {
00224     LOGPRINTF("entry");
00225     g_assert(attrs.data != 0);
00226     g_assert(attrs.desired_color_depth == 8);
00227 
00228     ImagePageAttrs new_attrs(attrs);
00229     new_attrs.data = 0;
00230     guchar *src, *dst;
00231     gint x, y;
00232 
00233     // Convert rotation attribute to GDK value.
00234     switch (attrs.final_rotation)
00235     {
00236         case Clockwise_Degrees_0:
00237         case Clockwise_Degrees_360:
00238             // Nothing to do.
00239             LOGPRINTF("No need to rotate.");
00240             break;
00241 
00242         case Clockwise_Degrees_90:
00243             // Determine attributes for rotated image.
00244             new_attrs.desired_width  = attrs.desired_height;
00245             new_attrs.desired_height = attrs.desired_width;
00246             new_attrs.row_stride     = ImageDither::get_rowstride(new_attrs.desired_width, 1, 4);
00247 
00248             // Allocate memory for new bitmap.
00249             new_attrs.data = new unsigned char[ new_attrs.row_stride * new_attrs.desired_height ];
00250             if (new_attrs.data == 0)
00251             {
00252                 ERRORPRINTF("Failed to allocate new bitmap.");
00253                 return false;
00254             }
00255 
00256             // And fill the bitmap with the rotated image.
00257             for (y = 0; y < attrs.desired_height; y++)
00258             {
00259                 src = attrs.data
00260                        + (y * attrs.row_stride);                // 'y' rows from top
00261                 dst = new_attrs.data
00262                        + (new_attrs.desired_width - y - 1);     // 'y' columns from right
00263                 for (x = 0; x < attrs.desired_width; x++)
00264                 {
00265                     *dst = *src;
00266                     src = src + 1;                      // move one column right
00267                     dst = dst + new_attrs.row_stride;   // move one row down
00268                 }
00269             }
00270             break;
00271 
00272         case Clockwise_Degrees_180:
00273             // Allocate memory for new bitmap.
00274             new_attrs.data = new unsigned char[ new_attrs.row_stride * new_attrs.desired_height ];
00275             if (new_attrs.data == 0)
00276             {
00277                 ERRORPRINTF("Failed to allocate new bitmap.");
00278                 return false;
00279             }
00280 
00281             // And fill the bitmap with the rotated image.
00282             for (y = 0; y < attrs.desired_height; y++)
00283             {
00284                 src = attrs.data
00285                        + (y * attrs.row_stride);                                    // 'y' rows from top
00286                 dst = new_attrs.data
00287                        + new_attrs.desired_width - 1                                // rightmost column
00288                        + ((attrs.desired_height - y - 1) * new_attrs.row_stride);   // 'y' rows from bottom
00289                 for (x = 0; x < attrs.desired_width; x++)
00290                 {
00291                     *dst = *src;
00292                     src = src + 1;      // move one column right
00293                     dst = dst - 1;      // move one column left
00294                 }
00295             }
00296             break;
00297 
00298         case Clockwise_Degrees_270:
00299             // Determine attributes for rotated image.
00300             new_attrs.desired_width  = attrs.desired_height;
00301             new_attrs.desired_height = attrs.desired_width;
00302             new_attrs.row_stride     = ImageDither::get_rowstride(new_attrs.desired_width, 1, 4);
00303 
00304             // Allocate memory for new bitmap.
00305             new_attrs.data = new unsigned char[ new_attrs.row_stride * new_attrs.desired_height ];
00306             if (new_attrs.data == 0)
00307             {
00308                 ERRORPRINTF("Failed to allocate new bitmap.");
00309                 return false;
00310             }
00311 
00312             // And fill the bitmap with the rotated image.
00313             for (y = 0; y < attrs.desired_height; y++)
00314             {
00315                 src = attrs.data
00316                        + (y * attrs.row_stride);                                    // 'y' rows from top
00317                 dst = new_attrs.data
00318                        + y                                                          // 'y' columns from left
00319                        + ((new_attrs.desired_height - 1) * new_attrs.row_stride);   // rightmost column
00320                 for (x = 0; x < attrs.desired_width; x++)
00321                 {
00322                     *dst = *src;
00323                     src = src + 1;                      // move one column right
00324                     dst = dst - new_attrs.row_stride;   // move one row up
00325                 }
00326             }
00327             break;
00328 
00329         default:
00330             ERRORPRINTF("invalid attrs.rotation [%d]", attrs.final_rotation);
00331             assert(false);
00332     }
00333 
00334     // Replace bitmap with new one, if needed.
00335     if (new_attrs.data)
00336     {
00337         delete[] attrs.data;
00338         attrs = new_attrs;
00339     }
00340 
00341     return true;
00342 }
00343 
00344 bool ImagePage::try_to_dither(void)
00345 {
00346     LOGPRINTF("entry");
00347     g_assert(loader != 0);
00348     g_assert(attrs.data == 0);
00349     
00350     if (attrs.original_color_depth == attrs.desired_color_depth)
00351     {
00352         return true;
00353     }
00354 
00355     // Currenty, only support dither 32bits or 24bits to 8bits.
00356     assert(attrs.desired_color_depth == 8);
00357 
00358     using namespace images;
00359     BitmapAttributes attrs_src;
00360     BitmapAttributes attrs_dst;
00361 
00362     attrs_src.width = attrs.desired_width;
00363     attrs_src.height = attrs.desired_height;
00364     attrs_src.rowstride = attrs.row_stride;
00365     attrs_src.data = gdk_pixbuf_get_pixels(loader);
00366     attrs_src.bytes_per_pixel = attrs.original_color_depth / 8;
00367 
00368     ImageDither dither;
00369     dither.dither_to_8bits(&attrs_src, &attrs_dst);
00370 
00371     // free the gdk_pixbuf, now that we have greyscale copy of it
00372     g_object_unref(loader);
00373     loader = 0;
00374     
00375     // update the ImagePageAttrs
00376     attrs.data = attrs_dst.data;
00377     attrs.row_stride = attrs_dst.rowstride;
00378     
00379     return true;
00380 }
00381 
00382 bool ImagePage::update_attributes(void)
00383 {
00384     LOGPRINTF("entry");
00385 
00386     // Pixbuf with 24bit or 32bit(alpha).
00387     if (loader)
00388     {
00389         attrs.row_stride     = gdk_pixbuf_get_rowstride(loader);
00390         attrs.desired_width  = gdk_pixbuf_get_width(loader);
00391         attrs.desired_height = gdk_pixbuf_get_height(loader);
00392         attrs.data           = 0;  // No copy of pixbuf data now, to optimise memory usage.
00393 
00394         LOGPRINTF("\noriginal_width = %d,"
00395                    "\noriginal_height = %d,"
00396                    "\ndesired_width = %d,"
00397                    "\ndesired_height = %d,"
00398                    "\nrow_stride = %d,"
00399                    "\noriginal_color_depth = %d,"
00400                    "\ndesired_color_depth = %d,"
00401                    "\nzoom = %f,"
00402                    "\nrotation = %d,"
00403                    "\nfinal_rotation = %d,"
00404                    "\ndata = %p",
00405                    attrs.original_width,
00406                    attrs.original_height,
00407                    attrs.desired_width,
00408                    attrs.desired_height,
00409                    attrs.row_stride,
00410                    attrs.original_color_depth,
00411                    attrs.desired_color_depth,
00412                    attrs.zoom,
00413                    attrs.rotation,
00414                    attrs.final_rotation,
00415                    attrs.data);
00416 
00417         return valid = true;
00418     }
00419 
00420     // It is not a valid image.
00421     return valid = false;
00422 }
00423 
00424 void ImagePage::destroy()
00425 {
00426     LOGPRINTF("entry");
00427 
00428     path.clear();
00429 
00430     attrs.original_width = 0;
00431     attrs.original_height = 0;
00432     attrs.desired_width = 0;
00433     attrs.desired_height = 0;
00434     attrs.row_stride = 0;
00435     attrs.original_color_depth = 0;
00436     attrs.desired_color_depth = DEFAULT_COLOR_DEPTH;
00437     attrs.zoom = DEFAULT_ZOOM_FACTOR;
00438     attrs.rotation = DEFAULT_ROTATION;
00439     attrs.final_rotation = DEFAULT_ROTATION;
00440     if (attrs.data)
00441     {
00442         delete [] attrs.data;
00443         attrs.data = 0;
00444     }
00445 
00446     display_width = 0;
00447     display_height = 0;
00448     
00449     timestamp = 0;
00450     valid = false;
00451 }
00452 
00453 // Define the hash function.
00454 size_t ImagePage::operator()(void) const
00455 {
00456     return calc_key(path, display_width, display_height, 
00457                     attrs.zoom, attrs.rotation);
00458 }
00459 
00460 size_t ImagePage::calc_key(const std::string & anchor,
00461                            int width,
00462                            int height,
00463                            float zoom,
00464                            int rotation)
00465 {
00466     struct PageKey
00467     {
00468         char   anchor[MAX_PATH_LEN]; 
00469         int    display_width;        // display size
00470         int    display_height;
00471         float  zoom;
00472         int    rotation;
00473     };
00474 
00475     PageKey key;
00476     int len = sizeof(PageKey);
00477     memset(&key, 0, len);
00478     
00479     memcpy(key.anchor, anchor.c_str(), strlen(anchor.c_str()));
00480     key.display_width = width;
00481     key.display_height = height;
00482     key.zoom = zoom;
00483     key.rotation = rotation;
00484 
00485     size_t h = make_hash_code((const char*)&key, len);
00486 
00487     LOGPRINTF("return %d", h);
00488 
00489     return h;
00490 }
00491 
00492 // Define the equal function.
00493 bool ImagePage::operator == (const ImagePage & right)
00494 {
00495     LOGPRINTF("entry");
00496 
00497     // Note: Ignore final_rotation for comparison
00498     if ((path == right.path)
00499         && (this->attrs.zoom == right.attrs.zoom)
00500         && (this->attrs.rotation == right.attrs.rotation)
00501         && (this->display_width == right.display_width)
00502         && (this->display_height == right.display_height))
00503     {
00504         return true;
00505     }
00506 
00507     return false;
00508 }
00509 
00510 // Define operator <
00511 bool ImagePage::operator < (const ImagePage & right)
00512 {
00513    if (this->in_use && !right.in_use)
00514    {
00515        return false;
00516    }
00517    else if (!this->in_use && right.in_use)
00518    {
00519        return true;
00520    }
00521    else
00522    {
00523         LOGPRINTF("%ld < %ld ? %d", 
00524                 this->timestamp, right.timestamp, 
00525                 this->timestamp < right.timestamp);
00526 
00527         return (this->timestamp < right.timestamp);
00528    }
00529 }
00530 
00531 // Define operator >
00532 bool ImagePage::operator > (const ImagePage & right)
00533 {
00534     return !((*this) < right);
00535 }
00536 
00537 // Get the approximate size of memory for the page.
00538 int ImagePage::length(void)
00539 {
00540     int size = attrs.row_stride * attrs.desired_height;
00541     
00542     LOGPRINTF("return %d", size);
00543 
00544     return size;
00545 }
00546 
00547 gint64 ImagePage::estimate_length(const ImagePageAttrs & request)
00548 
00549 {
00550     ImagePageAttrs attrs(request);
00551 
00552     calc_desired_dimension(attrs);
00553 
00554     // Memory size needed for bitmap to UDS.
00555     int rowstride = ImageDither::get_rowstride(attrs.desired_width, 
00556                                                attrs.desired_color_depth / 8,
00557                                                4);
00558     gint64 size_for_bitmap = static_cast<gint64>(rowstride) *
00559                              static_cast<gint64>(attrs.desired_height);
00560 
00561     // Memory size needed temporarily while rendering.
00562     gint64 size_for_tmp = size_for_bitmap * 5;
00563 
00564     // Memory size that must be available to render this image.
00565     gint64 size = size_for_bitmap + size_for_tmp;
00566     
00567     LOGPRINTF("original [%d %d] desired [%d %d] memory [%lld %lld %lld]",
00568               attrs.original_width, attrs.original_height,
00569               attrs.desired_width, attrs.desired_height,
00570               size_for_bitmap, size_for_tmp, size);
00571     
00572     return size;
00573 }
00574 
00575 void ImagePage::update_timestamp(void)
00576 {
00577 #ifdef WIN32
00578     timestamp = GetTickCount();
00579 #else
00580     struct sysinfo s_info;
00581     sysinfo(&s_info);
00582     timestamp = s_info.uptime;
00583 #endif
00584 }
00585 
00586 // This function calculates how to rotate the image such that it is displayed as large as possible.
00587 //
00588 // Rules for autorotation:
00589 //
00590 //   Ignore EXIF information from image file.
00591 //   No autorotate when:
00592 //     - image at 100% fits on screen
00593 //     - portrait image on portrait display
00594 //     - landscape image on landscape display
00595 //   Assume device rotation as in DR800 (landscape dipslay is device rotated anti-clockwise).
00596 //   Portrait image on landscape display:
00597 //     - device is rotated anti-clockwise (DR800 mode)
00598 //     - show the image as if device were not rotated
00599 //     - image autorotate clockwise
00600 //   Landscape image on portrait display:
00601 //     - device is not rotated
00602 //     - show the image as if device were rotated anti-clockwise (DR800 mode)
00603 //     - image autorotate anticlockwise
00604 PluginRotationDegree ImagePage::check_autorotate(int w, int h, int display_w, int display_h)
00605 {
00606     // Image fits on screen: no autorotation.
00607     if ((w <= display_w) && (h <= display_h))
00608     {
00609         return Clockwise_Degrees_0;
00610     }
00611 
00612     // Portrait image on landscape display: autorotate.
00613     if ((w < h) && (display_w > display_h))
00614     {
00615         return Clockwise_Degrees_270;
00616     }
00617 
00618     // Landscape image on portrait dislay: autorotate.
00619     if ((w > h) && (display_w < display_h))
00620     {
00621         return Clockwise_Degrees_90;
00622     }
00623 
00624     return Clockwise_Degrees_0;
00625 }
00626 
00627 };
00628 
00629 
Generated by  doxygen 1.6.2-20100208