]> git.wh0rd.org - tt-rss.git/commitdiff
Added average color calculation of feeds' favicons for banded display.
authormoontear <gaida@DE-ADN-F1FW4S1.adesso.local>
Mon, 15 Apr 2013 10:28:52 +0000 (12:28 +0200)
committermoontear <gaida@DE-ADN-F1FW4S1.adesso.local>
Mon, 15 Apr 2013 10:28:52 +0000 (12:28 +0200)
classes/feeds.php
include/functions.php
include/rssfuncs.php
lib/floIcon.php [new file with mode: 0644]
schema/versions/mysql/117.sql [new file with mode: 0644]

index c7b7a83e6acefe67d7e08221ec8fa2a0f792fe93..64d1d8786e090a74bd18969f17b67eef93327d18 100644 (file)
@@ -528,8 +528,9 @@ class Feeds extends Handler_Protected {
                                        $reply['content'] .= "<div class=\"cdm $expanded_class $class\"
                                                id=\"RROW-$id\" $mouseover_attrs'>";
 
-                                       $reply['content'] .= "<div class=\"cdmHeader\">";
-
+                                       //setting feed headline background color, needs to change text color based on dark/light
+                                       $favColor = explode("|", $line['favicon_avg_color']);
+                                       $reply['content'] .= "<div class=\"cdmHeader\" style=\"background-color: rgb($favColor[0], $favColor[1], $favColor[2]);\">";
                                        $reply['content'] .= "<div style=\"vertical-align : middle\">";
 
                                        $reply['content'] .= "<input dojoType=\"dijit.form.CheckBox\"
index e2357f6c0bad4627cdbdb8008324a5b61397dd74..1ae5bbbe2c6b1fcf711fd068e64ad92e0861ad84 100644 (file)
                                        }
                                }
                        }
+            return $icon_file;
                }
        }
+       
+       function calculate_avg_color($iconFile) {
+               
+               require_once "lib/floIcon.php";
+               
+               $imgInfo = @getimagesize($iconFile);
+        
+               if(strtolower($imgInfo['mime'])=='image/vnd.microsoft.icon') {
+                       $ico = new floIcon();
+                       @$ico->readICO($iconFile);
+                       //TODO: error logging
+                       if(count($ico->images)==0)
+                               return null;
+                       else {
+                               $image = @$ico->images[count($ico->images)-1]->getImageResource();
+                       }
+                       $type = "ico";
+                       }
+               elseif(strtolower($imgInfo['mime'])=='image/png') {
+            $image = imagecreatefrompng($iconFile);
+                       $type = 'png';
+               }
+               elseif(strtolower($imgInfo['mime'])=='image/jpeg') {
+                       $image = imagecreatefromjpeg($iconFile);
+                       $type = 'jpg';
+               }
+               elseif(strtolower($imgInfo['mime'])=='image/gif') {
+                       $image = imagecreatefromgif($iconFile);
+                       $type = 'gif';
+               }
+               //TODO: error logging
+               if (is_null($image))
+                       return null;
+               $width = imagesx($image);
+               $height = imagesy($image);
+               $pixel = imagecreatetruecolor(1, 1);
+               imagecopyresampled($pixel, $image, 0, 0, 0, 0, 1, 1, $width, $height);
+               $rgb = imagecolorat($pixel, 0, 0);
+               $color = imagecolorsforindex($pixel, $rgb);
+               return $color;
+       }
 
        function print_select($id, $default, $values, $attributes = "") {
                print "<select name=\"$id\" id=\"$id\" $attributes>";
index 859c575cce233a70754c24aee694c418d2a0cb7c..49870090615304338c84e48d84c5c70206a69866 100644 (file)
                        }
 
                        if ($favicon_needs_check) {
-                               check_feed_favicon($site_url, $feed, $link);
-
-                               db_query($link, "UPDATE ttrss_feeds SET favicon_last_checked = NOW()
+                               $favicon_file = check_feed_favicon($site_url, $feed, $link);
+                if ($favicon_file) {
+                                   $favicon_color = calculate_avg_color($favicon_file);
+                    if (is_array($favicon_color))
+                        $favicon_colorstring = ",favicon_avg_color = '" . implode("|", array_slice($favicon_color, 0, 3)) . "'";
+                }
+                               
+                               db_query($link, "UPDATE ttrss_feeds SET favicon_last_checked = NOW() $favicon_colorstring
                                        WHERE id = '$feed'");
                        }
 
diff --git a/lib/floIcon.php b/lib/floIcon.php
new file mode 100644 (file)
index 0000000..e9498b0
--- /dev/null
@@ -0,0 +1,843 @@
+<?php
+/***************************************************************************
+ *  Original floIcon copyright (C) 2007 by Joshua Hatfield.                *
+ *                                                                         *
+ *  In order to use any part of this floIcon Class, you must comply with   *
+ *  the license in 'license.doc'.  In particular, you may not remove this  *
+ *  copyright notice.                                                      *
+ *                                                                         *
+ *  Much time and thought has gone into this software and you are          *
+ *  benefitting.  We hope that you share your changes too.  What goes      *
+ *  around, comes around.                                                  *
+ ***************************************************************************
+
+Version 1.1.1:
+Date: 2009-03-16
+
+Changes:
+I was a little hasty on that last update.  A couple new bugs from 1.1.0 have
+been fixed.  
+
+Version 1.1.0:
+Date: 2009-03-16
+
+Changes:
+Added Vista support.  
+Fixed a number of minor bugs.  Many thanks to Dvir Berebi for pointing
+        them out.
+
+Version 1.0.5:
+Date: 2009-03-15
+
+Changes:
+Fixed bug when operating on low bit count images (1 or 4) with odd dimensions.
+
+Version 1.0.4:
+Date: 2007-05-25
+
+Changes:
+Made function not break quite so bad when reading a PNG file on a Vista icon.
+        Now, you shouldn't be loading Vista icons anyways, but since I'm trying to
+        upgrade to Vista compatible and I need a comparison, I've got to.
+
+Version 1.0.3:
+Date: 2007-05-25
+
+Changes:
+Okay, this one was just stupid.  When adding large image support, I messed up
+        and when reading, it doubled the image size.  Now, it's fixed.
+I took the opportunity to also add a dummy AND map for 32 images on those
+        readers who might be looking for it (though it's not supposed to be used.)
+
+Version 1.0.2:
+Date: 2007-05-24
+
+Sorry about two versions so quickly back to back, but something needed to be
+done with speed...things were getting too slow.  I'm sure you'll be okay.
+
+Changes:
+Told palette determination to stop at 257 colors or is 32 bit because the
+        palette is not used at that point and gets really slow when it starts
+        getting into the high numbers, for instance, in photographs or gradient
+        truecolor images with lots of unique colors.
+After experimenting, it appears that Windows XP does in fact support 256x256
+        images and larger by setting the entry value to 0 in the 1 byte that holds
+        that value and storing the true dimentions in the image header later.  Of
+        course, it still doesn't use these images in normal operation.  XP will
+        resize these and use them if no other images are available.
+Wrapped main documentation (this) to the 80th column for easier reading.
+
+Version 1.0.1:
+Date: 2007-05-23
+
+Thank you everyone for actively using the implementation on my site and
+illuminating me very quickly to a number of glaring bugs in this class.
+
+Changes:
+Added version history.
+Fixed bug with non-standard sizes in AND map reading AND writing.
+Fixed bug with palette images using non-black color in backgrounds.
+Fixed bug with height/width reversal reading files.
+
+
+Version 1.0.0:
+Date: 2007-05-17
+Original release date.
+
+
+Foreword:
+If you are simply in the effort of making an ICO file, may I recommend visiting
+my site, http://www.flobi.com/ , and clicking on floIcon.  I have a fully
+functional implementation (on which the sample.php is based) where you can also
+see recent icons submitted by other visitors.  No registration required, no
+intrusive ads.  (As of this writing, there aren't actually any ads at all, I
+might add google ads at some point.)
+
+If you are trying to get an idea of how ICO files, work, may I recommend the
+page I used, http://www.daubnet.com/formats/ICO.html .  It does not fully cover
+icon files, but it does a very good job of what it does.  Any additional
+information, I will try to post at
+http://www.flobi.com/test/floIcon/more_on_icons.php for your convenience.
+
+If you are trying to get an idea of how image resource files work, I recommend
+ANY other class that deals with images.  This class essentially plots points on
+the image, and that's not perticularly advanced.
+
+For any purpose, I wish you luck and feel free to contact me with any bugs,
+comments, questions, etc.  - Flobi
+
+Summary:
+This class parses ICO files.  It reads directly from the ICO file, headers
+first, so that if you are only retrieving 1 image, the entire ICO file need not
+be parsed.  It supports merging ICO files.  It supports adding PHP image
+resources as new images to the file.  It has an export capability that can
+easily be written to a new (or the same) ICO file for saving ICO files.  All
+sizes from 1x1 to 255x255 pixels and 1, 4, 8, 24 (plus transparency) and 32 bit
+images are supported.  Image retrieval by size is supported.
+
+Included is a fully functional sample that allows users to upload ICO, GIF,
+JPEG and PNG files into a session icon file as well as remove images from the
+file and download the completed file (sample.php).
+
+Known Limitations: Does not support Vista icons.  Does not support inversion
+palette method (because of the limitations of the PHP image resource).
+
+Addendum on Limitations:
+Windows Vista has added support for 256x256 size icons and now stores files as
+PNG's.  This class is for older ICO files.  A new class is currently under
+development that supports the new Windows Vista format.
+
+Palette inversion (my name for this technique) is the technique of using a black
+pixel (0, 0, 0) with a 1 "AND" pixel.  White pixels with a 1 "AND" show
+transparent (or "don't" show).  Black pixels with a 1 "AND" show inverted
+(sometimes).  Because this method isn't uniformly supported or documented and
+the PHP image resource doesn't support it, I have decided to not as well.  This
+does not apply to 32 bit images which include alpha transparency and no AND map.
+
+Though other functions exist, these are the only ones I believe offer usefulness
+to be public.
+floIcon public functions:
+        readICO($file, $offset = 0)
+                Loads the icon from the specified filepath ($file) starting at the
+                specified file offset ($offset).  This function MERGES the loaded icon
+                images into the floIcon object.
+
+        formatICO($offset = 0)
+                Returns the current floIcon object formatted as an .ICO file with the
+                file offset altered by $offset.  If there are too many or too large
+                images, causing any images saved past the 4,294,967,296th byte, this
+                will return false.  (This is well outside PHP's default memory
+                allocation.)
+
+        addImage($imageResource, $desiredBitCount = 1, $pngIfWidthExceeds = 48)
+                Adds an icon image to the icon file based on the passed image resource
+                ($imageResource).  It will automatically determine the bit count, but
+                can be persuaded to increase that to $desiredBitCount if that value is
+                greater than the determined bit count.
+
+                NOTE: The image resource is saved by REFERRENCE.  So, if you edit it
+                then call getImage, the resource returned will be the same, editions and
+                all.  Destruction of the resource will cause a new resource to be
+                created in getImage().
+
+        getImage($offset)
+                Returns the php image resource either assigned by addImage or created
+                dynamically at calltime by the data loaded with readICO().  The offset
+                specified here ($offset) is the array offset starting at 0 and ending
+                at countImages().
+
+        getBestImage($height = 32, $width = 32)
+                Returns the php images resource of the highest quality image closest to
+                the size specified.  This could be useful when you only want to display
+                the icon in an icon list with a single representative icon.  A resized
+                copy of the highest quality available image will be returned if there is
+                no 32 or 24 bit icon present at the speficied dimentions.
+
+        sortImagesBySize()
+                Sorts the $this->images array by order of size, smallest to largest.
+                This is the optimal sorting for icon files.
+
+        countImages()
+                Returns a count of how many images are present in the current floIcon
+                object.
+
+floIcon public variables:
+        $images
+                Contains a numerically indexed array of floIconImage objects.
+        $updated
+                True if an image has been added since load or last formatICO, otherwise
+                false.
+
+floIconImage public functions:
+        getHeader()
+                Returns an associative array containing the information from the ICO
+                image header.
+
+        getEntry()
+                Returns an associative array containing the information from the ICO
+                entry header.
+
+                NOTE: If this icon image was created by php image resource, this may not
+                have accurate information until saving from floIcon with the formatICO()
+                function.  Specifically, offset information will be inaccurate.
+
+        getImageResource()
+                Returns a php image resource.  Same as floIcon's getImage() function.
+
+        setImageResource($imageResource, $desiredBitCount = 1, $pngIfWidthExceeds = 48)
+                Changes this icon image based on the passed image resource
+                ($imageResource). It will automatically determine the bit count, but can
+                be persuaded to increase that to $desiredBitCount if that value is
+                greater than the determined bit count.
+
+                NOTE: The image resource is saved by REFERRENCE.  So, if you edit it
+                then call getImageResource, the resource returned will be the same,
+                editions and all.  Destruction of the resource will cause a new resource
+                to be created in getImageResource().
+
+        dealocateResource()
+                This destroys the image resource variable, freeing up memory.  The image
+                will automatically be recreated when getImageResource is executed.
+*/
+class floIcon {
+        /*
+         * $images is an associative array of offset integer => floIconImage object
+         */
+        var $images; // Array of floIconImage objects.
+        var $updated = false;
+        function floIcon() {
+                $this->images = array();
+        }
+        function countImages() {
+                return count($this->images);
+        }
+        function getBestImage($height = 32, $width = 32) {
+                $best = false;
+                $bestEntry = array();
+                $secondBest = false;
+                $secondBestEntry = array();
+                foreach ($this->images as $key => $image) {
+                        $entry = $image->getEntry();
+                        $header = $image->getHeader();
+                        if (!@$entry["BitCount"]) {
+                                $entry["BitCount"] = $header["BitCount"];
+                        }
+                        if ($entry["Height"] == $height && $entry["Width"] == $width && $entry["BitCount"] == 32) {
+                                return $image->getImageResource();
+                        } elseif ($entry["Height"] == $height && $entry["Width"] == $width && $entry["BitCount"] > min(4, @$bestEntry["BitCount"])) {
+                                $best = $image;
+                                $bestEntry = $entry;
+                        } elseif (
+                                !$secondBest or
+                                $entry["Height"] >= $secondBestEntry["Height"] &&
+                                $entry["Width"] >= $secondBestEntry["Width"] &&
+                                $secondBestEntry["BitCount"] >= $secondBestEntry["BitCount"] and
+                                (
+                                        $entry["Height"] <= 64 && $entry["Height"] > $secondBestEntry["Height"] and
+                                        $entry["Height"] > 64 && $entry["Height"] < $secondBestEntry["Height"]
+                                ) ||
+                                (
+                                        $entry["Width"] <= 64 && $entry["Width"] > $secondBestEntry["Width"] and
+                                        $entry["Width"] > 64 && $entry["Width"] < $secondBestEntry["Width"]
+                                ) ||
+                                $secondBestEntry["BitCount"] > $secondBestEntry["BitCount"]
+                                ) {
+                                $secondBest = $image;
+                                $secondBestEntry = $entry;
+                        }
+                }
+                if ($best) {
+                        return $best->getImageResource();
+                } elseif ($secondBest) {
+                        if ($secondBestEntry["Width"] != $width || $secondBestEntry["Height"] != $height) {
+                                $imageResource = $secondBest->getImageResource();
+                                $newImageResource = imagecreatetruecolor($width, $height);
+                                imagesavealpha($newImageResource, true);
+                                imagealphablending($newImageResource, false);
+                                imagecopyresampled($newImageResource, $imageResource, 0, 0, 0, 0, $width, $height, $secondBestEntry["Width"], $secondBestEntry["Height"]);
+                                $this->addImage($newImageResource, 32);
+                                return $newImageResource;
+                        } else {
+                                return $secondBest->getImageResource();
+                        }
+                }
+        }
+        /*
+         * readICO merges the icon images from the file to the current list
+         */
+        function readICO($file, $offset = 0) {
+                if (file_exists($file) && filesize($file) > 0 && $filePointer = fopen($file, "r")) {
+                        fseek($filePointer, $offset);
+                        $header = unpack("SReserved/SType/SCount", fread($filePointer, 6));
+                        for ($t = 0; $t < $header["Count"]; $t++) {
+                                $newImage = new floIconImage();
+                                $newImage->readImageFromICO($filePointer, 6 + ($t * 16));
+                                $this->images[] = $newImage;
+                        }
+                        fclose($filePointer);
+                }
+        }
+        function sortImagesBySize() {
+                usort($this->images, array("floIcon", "_cmpObj"));
+        }
+        function formatICO($offset = 0) {
+                $this->updated = false;
+                $output = "";
+                $output .= pack("SSS", 0, 1, count($this->images));
+                $output_images = "";
+                foreach ($this->images as $image) {
+                        $newImageOffset = $offset + // Whatever offset we've been given.
+                                6 // Header.
+                                + (count($this->images) * 16) // Entries.
+                                + strlen($output_images);
+                        if ($newImageOffset > pow(256, 4) /* 4 bytes available for position */ ) {
+                                return false;
+                        }
+                        $output .= $image->formatEntryForIco($newImageOffset); // The images already in there.
+                        $output_images .= $image->formatImageForIco();
+                }
+                return $output.$output_images;
+        }
+        function _cmpObj($a, $b) {
+                $aSize = $a->getSize();
+                $bSize = $b->getSize();
+                if ($aSize == $bSize) {
+                        return 0;
+                }
+                return ($aSize > $bSize)?1:-1;
+        }
+
+        function addImage($imageResource, $desiredBitCount = 1, $pngIfWidthExceeds = 48) {
+                $this->updated = true;
+                $newImage = new floIconImage();
+                $newImage->setImageResource($imageResource, $desiredBitCount, $pngIfWidthExceeds);
+                $this->images[] = $newImage;
+        }
+        function getImage($offset) {
+                if (isset($this->images[$offset])) {
+                        return $this->images[$offset]->getImageResource();
+                } else {
+                        return false;
+                }
+        }
+        /*
+         * getSize computes the
+         */
+        function getSize() {
+                // Compute headers.
+                $computedSize = 6; // Always 6 bytes.
+                // Add image entry headers
+                $computedSize += count($this->images) * 16; // Entry headers are always 16 bytes.
+                foreach ($this->images as $image) {
+                        $computedSize += $image->getSize() + $image->getHeaderSize(); // getSize does not include the header.
+                }
+        }
+}
+class floIconImage {
+        var $_imageResource = null;
+        var $_entry = "";
+        var $_entryIconFormat = "";
+        var $_header = "";
+        var $_headerIconFormat = "";
+        var $_imageIconFormat = ""; // Includes palette and mask.
+        function formatEntryForIco($offset) {
+                // Format the entry, this has to be done here because we need the offset to get the full information.
+                $this->_entry["FileOffset"] = $offset;
+                $this->_entryIconFormat = pack("CCCCSSLL",
+                        $this->_entry["Width"]>=256?0:$this->_entry["Width"],
+                        $this->_entry["Height"]>=256?0:$this->_entry["Height"],
+                        $this->_entry["ColorCount"],
+                        $this->_entry["Reserved"],
+                        $this->_entry["Planes"],
+                        $this->_entry["BitCount"],
+                        $this->_entry["SizeInBytes"],
+                        $this->_entry["FileOffset"]
+                );
+                return $this->_entryIconFormat;
+        }
+        function formatImageForIco() {
+                // Format the entry, this has to be done here because we need the offset to get the full information.
+                return ($this->_headerIconFormat.$this->_imageIconFormat);
+        }
+
+        // Will move $bitCount UP to $desiredBitCount if $bitCount is found to be less than it.
+        function setImageResource($imageResource, $desiredBitCount = 1, $pngIfWidthExceeds = 48) {
+                imagesavealpha($imageResource, true);
+                imagealphablending($imageResource, false);
+                $height = imagesy($imageResource);
+                $width = imagesx($imageResource);
+               
+                // Parse resource to determine header and icon format
+
+                // Find Palette information
+                $is_32bit = false; // Start with an assumption and get proven wrong.
+                $hasTransparency = 0;
+                $blackColor = false;
+                $bitCount = 0;
+                $realPalette = array();
+                $realIndexPalette = array();
+                for ($x = 0; $x < $width && !$is_32bit; $x++) {
+                        for ($y = 0; $y < $height && !$is_32bit; $y++) {
+                                $colorIndex = imagecolorat($imageResource, $x, $y);
+                                $color = imagecolorsforindex($imageResource, $colorIndex);
+                                if ($color["alpha"] == 0) {
+                                        // No point continuing if there's more than 256 colors or it's 32bit.
+                                        if (count($realPalette) < 257 && !$is_32bit) {
+                                                $inRealPalette = false;
+                                                foreach($realPalette as $realPaletteKey => $realPaletteColor) {
+                                                        if (
+                                                                $color["red"] == $realPaletteColor["red"] and
+                                                                $color["green"] == $realPaletteColor["green"] and
+                                                                $color["blue"] == $realPaletteColor["blue"]
+                                                        ) {
+                                                                $inRealPalette = $realPaletteKey;
+                                                                break;
+                                                        }
+                                                }
+                                                if ($inRealPalette === false) {
+                                                        $realIndexPalette[$colorIndex] = count($realPalette);
+                                                        if (
+                                                                $blackColor === false and
+                                                                $color["red"] == 0 and
+                                                                $color["green"] == 0 and
+                                                                $color["blue"] == 0
+                                                        ) {
+                                                                $blackColor = count($realPalette);
+                                                        }
+                                                        $realPalette[] = $color;
+                                                } else {
+                                                        $realIndexPalette[$colorIndex] = $inRealPalette;
+                                                }
+                                        }
+                                } else {
+                                        $hasTransparency = 1;
+                                }
+                                if ($color["alpha"] != 0 && $color["alpha"] != 127) {
+                                        $is_32bit = true;
+                                }
+                        }
+                }
+                if ($is_32bit) {
+                        $colorCount = 0;
+                        $bitCount = 32;
+                } else {
+                        if ($hasTransparency && $blackColor === false) {
+                                // We need a black color to facilitate transparency.  Unfortunately, this can
+                                // increase the palette size by 1 if there's no other black color.
+                                $blackColor = count($realPalette);
+                                $color = array(
+                                        "red" => 0,
+                                        "blue" => 0,
+                                        "green" => 0,
+                                        "alpha" => 0
+                                );
+                                $realPalette[] = $color;
+                        }
+                        $colorCount = count($realPalette);
+                        if ($colorCount > 256 || $colorCount == 0) {
+                                $bitCount = 24;
+                        } elseif ($colorCount > 16) {
+                                $bitCount = 8;
+                                // 8 bit
+                        } elseif ($colorCount > 2) {
+                                $bitCount = 4;
+                                // 4 bit
+                        } else {
+                                $bitCount = 1;
+                                // 1 bit
+                        }
+                        if ($desiredBitCount > $bitCount) {
+                                $bitCount = $desiredBitCount;
+                        }
+                        switch ($bitCount) {
+                                case 24:
+                                        $colorCount = 0;
+                                        break;
+                                case 8:
+                                        $colorCount = 256;
+                                        break;
+                                case 4:
+                                        $colorCount = 16;
+                                        break;
+                                case 1:
+                                        $colorCount = 2;
+                                        break;
+                        }
+                }
+                // Create $this->_imageIconFormat...
+                $this->_imageIconFormat = "";
+                if ($bitCount < 24) {
+                        $iconPalette = array();
+                        // Save Palette
+                        foreach ($realIndexPalette as $colorIndex => $paletteIndex) {
+                                $color = $realPalette[$paletteIndex];
+                                $this->_imageIconFormat .= pack("CCCC", $color["blue"], $color["green"], $color["red"], 0);
+                        }
+                        while (strlen($this->_imageIconFormat) < $colorCount * 4) {
+                                $this->_imageIconFormat .= pack("CCCC", 0, 0, 0, 0);
+                        }
+                        // Save Each Pixel as Palette Entry
+                        $byte = 0; // For $bitCount < 8 math
+                        $bitPosition = 0; // For $bitCount < 8 math
+                        for ($y = 0; $y < $height; $y++) {
+                                for ($x = 0; $x < $width; $x++) {
+                                        $color = imagecolorat($imageResource, $x, $height-$y-1);
+                                        if (isset($realIndexPalette[$color])) {
+                                                $color = $realIndexPalette[$color];
+                                        } else {
+                                                $color = $blackColor;
+                                        }
+
+                                        if ($bitCount < 8) {
+                                                $bitPosition += $bitCount;
+                                                $colorAdjusted = $color * pow(2, 8 - $bitPosition);
+                                                $byte += $colorAdjusted;
+                                                if ($bitPosition == 8) {
+                                                        $this->_imageIconFormat .= chr($byte);
+                                                        $bitPosition = 0;
+                                                        $byte = 0;
+                                                }
+                                        } else {
+                                                $this->_imageIconFormat .= chr($color);
+                                        }
+                                }
+                                // Each row ends with dumping the remaining bits and filling up to the 32bit line with 0's.
+                                if ($bitPosition) {
+                                        $this->_imageIconFormat .= chr($byte);
+                                        $bitPosition = 0;
+                                        $byte = 0;
+                                }
+                                if (strlen($this->_imageIconFormat)%4) $this->_imageIconFormat .= str_repeat(chr(0), 4-(strlen($this->_imageIconFormat)%4));
+                        }
+                } else {
+                        // Save each pixel.
+                        for ($y = 0; $y < $height; $y++) {
+                                for ($x = 0; $x < $width; $x++) {
+                                        $color = imagecolorat($imageResource, $x, $height-$y-1);
+                                        $color = imagecolorsforindex($imageResource, $color);
+                                        if ($bitCount == 24) {
+                                                if ($color["alpha"]) {
+                                                        $this->_imageIconFormat .= pack("CCC", 0, 0, 0);
+                                                } else {
+                                                        $this->_imageIconFormat .= pack("CCC", $color["blue"], $color["green"], $color["red"]);
+                                                }
+                                        } else {
+                                                $color["alpha"] = round((127-$color["alpha"]) / 127 * 255);
+                                                $this->_imageIconFormat .= pack("CCCC", $color["blue"], $color["green"], $color["red"], $color["alpha"]);
+                                        }
+                                }
+                                if (strlen($this->_imageIconFormat)%4) $this->_imageIconFormat .= str_repeat(chr(0), 4-(strlen($this->_imageIconFormat)%4));
+                        }
+                }
+                // save AND map (transparency)
+                $byte = 0; // For $bitCount < 8 math
+                $bitPosition = 0; // For $bitCount < 8 math
+                for ($y = 0; $y < $height; $y++) {
+                        for ($x = 0; $x < $width; $x++) {
+                                if ($bitCount < 32) {
+                                        $color = imagecolorat($imageResource, $x, $height-$y-1);
+                                        $color = imagecolorsforindex($imageResource, $color);
+                                        $color = $color["alpha"] == 127?1:0;
+                                } else {
+                                        $color = 0;
+                                }
+
+                                $bitPosition += 1;
+                                $colorAdjusted = $color * pow(2, 8 - $bitPosition);
+                                $byte += $colorAdjusted;
+                                if ($bitPosition == 8) {
+                                        $this->_imageIconFormat .= chr($byte);
+                                        $bitPosition = 0;
+                                        $byte = 0;
+                                }
+                        }
+                        // Each row ends with dumping the remaining bits and filling up to the 32bit line with 0's.
+                        if ($bitPosition) {
+                                $this->_imageIconFormat .= chr($byte);
+                                $bitPosition = 0; // For $bitCount < 8 math
+                                $byte = 0;
+                        }
+                        while (strlen($this->_imageIconFormat)%4) {
+                                $this->_imageIconFormat .= chr(0);
+                        }
+                }
+                if ($colorCount >= 256) {
+                        $colorCount = 0;
+                }
+                // Create header information...
+                $this->_header = array(
+                        "Size" => 40,
+                        "Width" => $width,
+                        "Height" => $height*2,
+                        "Planes" => 1,
+                        "BitCount" => $bitCount,
+                        "Compression" => 0,
+                        "ImageSize" => strlen($this->_imageIconFormat),
+                        "XpixelsPerM" => 0,
+                        "YpixelsPerM" => 0,
+                        "ColorsUsed" => $colorCount,
+                        "ColorsImportant" => 0,
+                );
+                $this->_headerIconFormat = pack("LLLSSLLLLLL",
+                        $this->_header["Size"],
+                        $this->_header["Width"],
+                        $this->_header["Height"],
+
+                        $this->_header["Planes"],
+                        $this->_header["BitCount"],
+
+                        $this->_header["Compression"],
+                        $this->_header["ImageSize"],
+                        $this->_header["XpixelsPerM"],
+                        $this->_header["YpixelsPerM"],
+                        $this->_header["ColorsUsed"],
+                        $this->_header["ColorsImportant"]
+                );
+                $this->_entry = array(
+                        "Width" => $width,
+                        "Height" => $height,
+                        "ColorCount" => $colorCount,
+                        "Reserved" => 0,
+                        "Planes" => 1,
+                        "BitCount" => $bitCount,
+                        "SizeInBytes" => $this->_header["Size"] + $this->_header["ImageSize"],
+                        "FileOffset" => -1,
+                );
+                $this->_entryIconFormat = ""; // This won't get set until it's needed with the offset.
+                $this->_imageResource = $imageResource;
+
+                // Make png if width exceeds limit for old ico style
+                if ($width > $pngIfWidthExceeds) {
+                        // I wish there were a better way to get the info than this.  If anyone needs a version that doesn't use OB, I can have one that creates a TMP file.
+                        ob_start();
+                        imagepng($imageResource);
+                        $imageAsPng = ob_get_contents();
+                        ob_end_clean();
+                        $this->_headerIconFormat = "";
+                        $this->_imageIconFormat = $imageAsPng;
+                }
+
+       
+        }
+        function _createImageResource() {
+                if ($newImage = @imagecreatefromstring($this->_headerIconFormat.$this->_imageIconFormat)) {
+                        // Vista supports PNG.
+                        $this->_headerIconFormat = "";
+                        $this->_imageIconFormat = $this->_headerIconFormat.$this->_imageIconFormat;
+                        imagesavealpha($newImage, true);
+                        imagealphablending($newImage, false);
+                        $this->_imageResource = $newImage;
+                } elseif ($this->_entry["Height"] <= 1024 && $this->_entry["Width"] <= 1024) {
+                        $newImage = imagecreatetruecolor($this->_entry["Width"], $this->_entry["Height"]);
+                        imagesavealpha($newImage, true);
+                        imagealphablending($newImage, false);
+                        $readPosition = 0;
+                        $palette = array();
+                        if ($this->_header["BitCount"] < 24) {
+                                // Read Palette for low bitcounts
+                                $colorsInPalette = $this->_header["ColorsUsed"]?$this->_header["ColorsUsed"]:$this->_entry["ColorCount"];
+                                for ($t = 0; $t < pow(2, $this->_header["BitCount"]); $t++) {
+                                        $blue = ord($this->_imageIconFormat[$readPosition++]);
+                                        $green = ord($this->_imageIconFormat[$readPosition++]);
+                                        $red = ord($this->_imageIconFormat[$readPosition++]);
+                                        $readPosition++; // Unused "Reserved" value.
+                                                $existingPaletteEntry = imagecolorexactalpha($newImage, $red, $green, $blue, 0);
+                                                if ($existingPaletteEntry >= 0) {
+                                                        $palette[] = $existingPaletteEntry;
+                                                } else {
+                                                        $palette[] = imagecolorallocatealpha($newImage, $red, $green, $blue, 0);
+                                                }
+                                }
+                                // XOR
+                                for ($y = 0; $y < $this->_entry["Height"]; $y++) {
+                                        $colors = array();
+                                        for ($x = 0; $x < $this->_entry["Width"]; $x++) {
+                                                if ($this->_header["BitCount"] < 8) {
+                                                        $color = array_shift($colors);
+                                                        if (is_null($color)) {
+                                                                $byte = ord($this->_imageIconFormat[$readPosition++]);
+                                                                $tmp_color = 0;
+                                                                for ($t = 7; $t >= 0; $t--) {
+                                                                        $bit_value = pow(2, $t);
+                                                                        $bit = floor($byte / $bit_value);
+                                                                        $byte = $byte - ($bit * $bit_value);
+                                                                        $tmp_color += $bit * pow(2, $t%$this->_header["BitCount"]);
+                                                                        if ($t%$this->_header["BitCount"] == 0) {
+                                                                                array_push($colors, $tmp_color);
+                                                                                $tmp_color = 0;
+                                                                        }
+                                                                }
+                                                                $color = array_shift($colors);
+                                                        }
+                                                } else {
+                                                        $color = ord($this->_imageIconFormat[$readPosition++]);
+                                                }
+                                                imagesetpixel($newImage, $x, $this->_entry["Height"]-$y-1, $palette[$color]) or die("can't set pixel");
+                                        }
+                                        // All rows end on the 32 bit
+                                        if ($readPosition%4) $readPosition += 4-($readPosition%4);
+                                }
+                        } else {
+                                // BitCount >= 24, No Palette.
+                                // marking position because some icons mark all pixels transparent when using an AND map.
+                                $markPosition = $readPosition;
+                                $retry = true;
+                                $ignoreAlpha = false;
+                                while ($retry) {
+                                        $alphas = array();
+                                        $retry = false;
+                                        for ($y = 0; $y < $this->_entry["Height"] and !$retry; $y++) {
+                                                for ($x = 0; $x < $this->_entry["Width"] and !$retry; $x++) {
+                                                        $blue = ord($this->_imageIconFormat[$readPosition++]);
+                                                        $green = ord($this->_imageIconFormat[$readPosition++]);
+                                                        $red = ord($this->_imageIconFormat[$readPosition++]);
+                                                        if ($this->_header["BitCount"] < 32) {
+                                                                $alpha = 0;
+                                                        } elseif($ignoreAlpha) {
+                                                                $alpha = 0;
+                                                                $readPosition++;
+                                                        } else {
+                                                                $alpha = ord($this->_imageIconFormat[$readPosition++]);
+                                                                $alphas[$alpha] = $alpha;
+                                                                $alpha = 127-round($alpha/255*127);
+                                                        }
+                                                        $paletteEntry = imagecolorexactalpha($newImage, $red, $green, $blue, $alpha);
+                                                        if ($paletteEntry < 0) {
+                                                                $paletteEntry = imagecolorallocatealpha($newImage, $red, $green, $blue, $alpha);
+                                                        }
+                                                        imagesetpixel($newImage, $x, $this->_entry["Height"]-$y-1, $paletteEntry) or die("can't set pixel");
+                                                }
+                                                if ($readPosition%4) $readPosition += 4-($readPosition%4);
+                                        }
+                                        if ($this->_header["BitCount"] == 32 && isset($alphas[0]) && count($alphas) == 1) {
+                                                $retry = true;
+                                                $readPosition = $markPosition;
+                                                $ignoreAlpha = true;
+                                        }
+                                }
+
+                        }
+                        // AND map
+                        if ($this->_header["BitCount"] < 32 || $ignoreAlpha) {
+                                // Bitcount == 32, No AND (if using alpha).
+                                $palette[-1] = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
+                                imagecolortransparent($newImage, $palette[-1]);
+                                for ($y = 0; $y < $this->_entry["Height"]; $y++) {
+                                        $colors = array();
+                                        for ($x = 0; $x < $this->_entry["Width"]; $x++) {
+                                                $color = array_shift($colors);
+                                                if (is_null($color)) {
+                                                        $byte = ord($this->_imageIconFormat[$readPosition++]);
+                                                        $tmp_color = 0;
+                                                        for ($t = 7; $t >= 0; $t--) {
+                                                                $bit_value = pow(2, $t);
+                                                                $bit = floor($byte / $bit_value);
+                                                                $byte = $byte - ($bit * $bit_value);
+                                                                array_push($colors, $bit);
+                                                        }
+                                                        $color = array_shift($colors);
+                                                }
+                                                if ($color) {
+                                                        imagesetpixel($newImage, $x, $this->_entry["Height"]-$y-1, $palette[-1]) or die("can't set pixel");
+                                                }
+                                        }
+                                        // All rows end on the 32 bit.
+                                        if ($readPosition%4) $readPosition += 4-($readPosition%4);
+                                }
+                        }
+                        if ($this->_header["BitCount"] < 24) {
+                                imagetruecolortopalette($newImage, true, pow(2, $this->_header["BitCount"]));
+                        }
+                }
+                $this->_imageResource = $newImage;
+        }
+        // this function expects that $_entry, $_header and $_imageIconFormat have already been read, specifically from readImageFromICO.
+        // Don't call this function except from there.
+        function readImageFromICO($filePointer, $entryOffset) {
+                $tmpPosition = ftell($filePointer); // So any other applications won't loose their position.
+                // Get the entry.
+                fseek($filePointer, $entryOffset);
+                $this->_entryIconFormat = fread($filePointer, 16);
+                $this->_entry = unpack("CWidth/CHeight/CColorCount/CReserved/SPlanes/SBitCount/LSizeInBytes/LFileOffset", $this->_entryIconFormat);
+
+                // Position the file pointer.
+                fseek($filePointer, $this->_entry["FileOffset"]);
+
+                // Get the header.
+                $this->_headerIconFormat = fread($filePointer, 40);
+        $this->_header = unpack("LSize/LWidth/LHeight/SPlanes/SBitCount/LCompression/LImageSize/LXpixelsPerM/LYpixelsPerM/LColorsUsed/LColorsImportant", $this->_headerIconFormat);
+
+                // Get the image.
+                $this->_imageIconFormat = @fread($filePointer, $this->_entry["SizeInBytes"] - strlen($this->_headerIconFormat));
+                fseek($filePointer, $tmpPosition); // So any other applications won't loose their position.
+
+                if ($newImage = @imagecreatefromstring($this->_headerIconFormat.$this->_imageIconFormat)) {
+                        // This is a PNG, the supposed header information is useless.
+                        $this->_header = array (
+                                "Size" => 0,
+                                "Width" => imagesx($newImage),
+                                "Height" => imagesy($newImage) * 2,
+                                "Planes" => 0,
+                                "BitCount" => 32,
+                                "Compression" => 0,
+                                "ImageSize" => strlen($this->_imageIconFormat),
+                                "XpixelsPerM" => 0,
+                                "YpixelsPerM" => 0,
+                                "ColorsUsed" => 0,
+                                "ColorsImportant" => 0,
+                        );
+                        imagedestroy($newImage);
+                }
+
+                // Support for larger images requires entry marked as 0.
+                if ($this->_entry["Width"] == 0) {
+                        $this->_entry["Width"] = $this->_header["Width"];
+                }
+                if ($this->_entry["Height"] == 0) {
+                        $this->_entry["Height"] = $this->_header["Height"]/2;
+                }
+        }
+        function getHeader() {
+                return $this->_header;
+        }
+        function getEntry() {
+                return $this->_entry;
+        }
+        function floIconImage() {
+        }
+        function getHeaderSize() {
+                return strlen($this->_headerIconFormat);
+        }
+        function getSize() {
+                return strlen($this->_imageIconFormat);
+        }
+        function getImageResource() {
+                if (!$this->_imageResource) $this->_createImageResource();
+                return $this->_imageResource;
+        }
+        function dealocateResource() {
+                @imagedestroy($this->_imageResource);
+                $this->_imageResource = null;
+        }
+}
+?>
\ No newline at end of file
diff --git a/schema/versions/mysql/117.sql b/schema/versions/mysql/117.sql
new file mode 100644 (file)
index 0000000..008f819
--- /dev/null
@@ -0,0 +1,7 @@
+begin;
+
+ALTER TABLE `ttrss_feeds` ADD favicon_avg_color VARCHAR(11)
+
+update ttrss_version set schema_version = 117;
+
+commit;