{"action":"created","comment":{"url":"https://api.github.com/repos/cargomedia/CM/pulls/comments/33254865","id":33254865,"diff_hunk":"@@ -0,0 +1,385 @@\n+<?php\n+\n+class CM_Image_Image {\n+\n+    const FORMAT_JPEG = 1;\n+    const FORMAT_GIF = 2;\n+    const FORMAT_PNG = 3;\n+\n+    /** @var Imagick|null */\n+    private $_imagick;\n+\n+    /** @var bool|null */\n+    private $_animated;\n+\n+    /**\n+     * @param string $imageBlob\n+     * @throws CM_Exception\n+     */\n+    public function __construct($imageBlob) {\n+        $imageBlob = (string) $imageBlob;\n+        try {\n+            $imagick = new Imagick();\n+            $imagick->readImageBlob($imageBlob);\n+            if ($imagick->getIteratorIndex() > 0) {\n+                $this->_animated = true;\n+                $imagick = $imagick->coalesceImages();\n+            } else {\n+                $this->_animated = false;\n+            }\n+        } catch (ImagickException $e) {\n+            throw new CM_Exception('Cannot load Imagick instance ' . $e->getMessage());\n+        }\n+        $this->_imagick = $imagick;\n+    }\n+\n+    public function __clone() {\n+        $this->_imagick = clone $this->_imagick;\n+    }\n+\n+    /**\n+     * @return $this\n+     */\n+    public function clearAndApplyExifRotation() {\n+        $orientation = $this->_imagick->getImageOrientation();\n+        switch ($orientation) {\n+            case Imagick::ORIENTATION_TOPRIGHT: // flipped\n+            case Imagick::ORIENTATION_UNDEFINED: // undefined\n+            case Imagick::ORIENTATION_TOPLEFT: // normal\n+                break;\n+            case Imagick::ORIENTATION_BOTTOMLEFT: // 180° flipped\n+            case Imagick::ORIENTATION_BOTTOMRIGHT: // 180°\n+                $this->rotate(-180);\n+                $this->_imagick->setImageOrientation(1);\n+                break;\n+            case Imagick::ORIENTATION_LEFTTOP: // 270° flipped\n+            case Imagick::ORIENTATION_RIGHTTOP: // 270°\n+                $this->rotate(-270);\n+                $this->_imagick->setImageOrientation(1);\n+                break;\n+            case Imagick::ORIENTATION_RIGHTBOTTOM: // 90° flipped\n+            case Imagick::ORIENTATION_LEFTBOTTOM: // 90°\n+                $this->rotate(-90);\n+                $this->_imagick->setImageOrientation(1);\n+                break;\n+        }\n+        return $this;\n+    }\n+\n+    /**\n+     * @param int      $width\n+     * @param int      $height\n+     * @param int|null $offsetX\n+     * @param int|null $offsetY\n+     * @return $this\n+     * @throws CM_Exception\n+     */\n+    public function crop($width, $height, $offsetX = null, $offsetY = null) {\n+        $width = (int) $width;\n+        $height = (int) $height;\n+        $offsetX = (null !== $offsetX) ? (int) $offsetX : null;\n+        $offsetY = (null !== $offsetY) ? (int) $offsetY : null;\n+\n+        if (null === $offsetX) {\n+            $offsetX = ($this->getWidth() - $width) / 2;\n+        }\n+        if (null === $offsetY) {\n+            $offsetY = ($this->getHeight() - $height) / 2;\n+        }\n+\n+        $this->_invokeOnEveryFrame(function (Imagick $frame) use ($width, $height, $offsetX, $offsetY) {\n+            $frame->cropImage($width, $height, $offsetX, $offsetY);\n+        });\n+        return $this;\n+    }\n+\n+    /**\n+     * @return string\n+     * @throws CM_Exception\n+     */\n+    public function getBlob() {\n+        try {\n+            if ($this->_getAnimationRequired($this->getFormat())) {\n+                $imageBlob = $this->_imagick->getImagesBlob();\n+            } else {\n+                $imageBlob = $this->_imagick->getImageBlob();\n+            }\n+        } catch (ImagickException $e) {\n+            throw new CM_Exception('Cannot get image blob: ' . $e->getMessage());\n+        }\n+        return $imageBlob;\n+    }\n+\n+    /**\n+     * @return CM_Image_Image\n+     */\n+    public function getClone() {\n+        return clone $this;\n+    }\n+\n+    /**\n+     * @return int\n+     * @throws CM_Exception_Invalid\n+     */\n+    public function getFormat() {\n+        $imagickFormat = $this->_imagick->getImageFormat();\n+        switch ($imagickFormat) {\n+            case 'JPEG':\n+                return self::FORMAT_JPEG;\n+            case 'GIF':\n+                return self::FORMAT_GIF;\n+            case'PNG':\n+                return self::FORMAT_PNG;\n+            default:\n+                throw new CM_Exception_Invalid('Unsupported format `' . $imagickFormat . '`.');\n+        }\n+    }\n+\n+    /**\n+     * @param int $format\n+     * @return $this\n+     * @throws CM_Exception\n+     */\n+    public function setFormat($format) {\n+        if (true !== $this->_imagick->setImageFormat($this->_getImagickFormat($format))) {\n+            throw new CM_Exception('Cannot set image format `' . $format . '`');\n+        }\n+        $this->_animated = $this->_getAnimationRequired($format);\n+        return $this;\n+    }\n+\n+    /**\n+     * @return int\n+     * @throws CM_Exception\n+     */\n+    public function getHeight() {\n+        try {\n+            return $this->_imagick->getImageHeight();\n+        } catch (ImagickException $e) {\n+            throw new CM_Exception('Cannot detect image height: ' . $e->getMessage());\n+        }\n+    }\n+\n+    /**\n+     * @return int\n+     * @throws CM_Exception\n+     */\n+    public function getWidth() {\n+        try {\n+            return $this->_imagick->getImageWidth();\n+        } catch (ImagickException $e) {\n+            throw new CM_Exception('Cannot detect image width: ' . $e->getMessage());\n+        }\n+    }\n+\n+    /**\n+     * @return bool\n+     */\n+    public function isAnimated() {\n+        return $this->_animated;\n+    }\n+\n+    /**\n+     * @param int       $widthMax\n+     * @param int       $heightMax\n+     * @param bool|null $square\n+     * @return $this\n+     * @throws CM_Exception\n+     */\n+    public function resize($widthMax, $heightMax, $square = null) {","path":"library/CM/Image/Image.php","position":189,"original_position":189,"commit_id":"634164879bdd4048ec20cea7b292937281027ab9","original_commit_id":"a91b1e8cd72ce289e3c6fe4ea1e60ae88925f501","user":{"login":"alexispeter","id":1921425,"avatar_url":"https://avatars.githubusercontent.com/u/1921425?v=3","gravatar_id":"","url":"https://api.github.com/users/alexispeter","html_url":"https://github.com/alexispeter","followers_url":"https://api.github.com/users/alexispeter/followers","following_url":"https://api.github.com/users/alexispeter/following{/other_user}","gists_url":"https://api.github.com/users/alexispeter/gists{/gist_id}","starred_url":"https://api.github.com/users/alexispeter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alexispeter/subscriptions","organizations_url":"https://api.github.com/users/alexispeter/orgs","repos_url":"https://api.github.com/users/alexispeter/repos","events_url":"https://api.github.com/users/alexispeter/events{/privacy}","received_events_url":"https://api.github.com/users/alexispeter/received_events","type":"User","site_admin":false},"body":"But let's keep it for a potential follow-up","created_at":"2015-06-25T13:36:32Z","updated_at":"2015-06-25T13:36:32Z","html_url":"https://github.com/cargomedia/CM/pull/1667#discussion_r33254865","pull_request_url":"https://api.github.com/repos/cargomedia/CM/pulls/1667","_links":{"self":{"href":"https://api.github.com/repos/cargomedia/CM/pulls/comments/33254865"},"html":{"href":"https://github.com/cargomedia/CM/pull/1667#discussion_r33254865"},"pull_request":{"href":"https://api.github.com/repos/cargomedia/CM/pulls/1667"}}},"pull_request":{"url":"https://api.github.com/repos/cargomedia/CM/pulls/1667","id":37706204,"html_url":"https://github.com/cargomedia/CM/pull/1667","diff_url":"https://github.com/cargomedia/CM/pull/1667.diff","patch_url":"https://github.com/cargomedia/CM/pull/1667.patch","issue_url":"https://api.github.com/repos/cargomedia/CM/issues/1667","number":1667,"state":"open","locked":false,"title":"Extract image-manipulation from CM_File_Image","user":{"login":"alexispeter","id":1921425,"avatar_url":"https://avatars.githubusercontent.com/u/1921425?v=3","gravatar_id":"","url":"https://api.github.com/users/alexispeter","html_url":"https://github.com/alexispeter","followers_url":"https://api.github.com/users/alexispeter/followers","following_url":"https://api.github.com/users/alexispeter/following{/other_user}","gists_url":"https://api.github.com/users/alexispeter/gists{/gist_id}","starred_url":"https://api.github.com/users/alexispeter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alexispeter/subscriptions","organizations_url":"https://api.github.com/users/alexispeter/orgs","repos_url":"https://api.github.com/users/alexispeter/repos","events_url":"https://api.github.com/users/alexispeter/events{/privacy}","received_events_url":"https://api.github.com/users/alexispeter/received_events","type":"User","site_admin":false},"body":"`CM_File_Image::resizeSpecific()` uses Imagick's `thumbnailImage()`.\r\nThis function strips all exif data - also \"Orientation\".\r\n\r\nSince Imagick cannot *write* changed exif information the only two solutions are:\r\n- Use `resizeImage()` instead of `thumbnailImage()` (keeps exif data, but file is bigger)\r\n- Rotate image data according to exif information manually.\r\n\r\nSee also http://stackoverflow.com/questions/7489742/php-read-exif-data-and-adjust-orientation\r\n\r\nExample images:\r\n- [rotated](https://cloud.githubusercontent.com/assets/360800/6355830/4387c938-bc59-11e4-8b79-e2dde71e921c.jpg)\r\n- [not rotated](https://cloud.githubusercontent.com/assets/360800/6355833/4dfaf6f6-bc59-11e4-8a7f-307fdae47432.jpg)\r\n","created_at":"2015-06-15T15:50:18Z","updated_at":"2015-06-25T13:36:32Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a503e211fb384b2347b7f4104690b1041ed87cc6","assignee":{"login":"alexispeter","id":1921425,"avatar_url":"https://avatars.githubusercontent.com/u/1921425?v=3","gravatar_id":"","url":"https://api.github.com/users/alexispeter","html_url":"https://github.com/alexispeter","followers_url":"https://api.github.com/users/alexispeter/followers","following_url":"https://api.github.com/users/alexispeter/following{/other_user}","gists_url":"https://api.github.com/users/alexispeter/gists{/gist_id}","starred_url":"https://api.github.com/users/alexispeter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alexispeter/subscriptions","organizations_url":"https://api.github.com/users/alexispeter/orgs","repos_url":"https://api.github.com/users/alexispeter/repos","events_url":"https://api.github.com/users/alexispeter/events{/privacy}","received_events_url":"https://api.github.com/users/alexispeter/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/cargomedia/CM/pulls/1667/commits","review_comments_url":"https://api.github.com/repos/cargomedia/CM/pulls/1667/comments","review_comment_url":"https://api.github.com/repos/cargomedia/CM/pulls/comments{/number}","comments_url":"https://api.github.com/repos/cargomedia/CM/issues/1667/comments","statuses_url":"https://api.github.com/repos/cargomedia/CM/statuses/634164879bdd4048ec20cea7b292937281027ab9","head":{"label":"alexispeter:image-exif-resize","ref":"image-exif-resize","sha":"634164879bdd4048ec20cea7b292937281027ab9","user":{"login":"alexispeter","id":1921425,"avatar_url":"https://avatars.githubusercontent.com/u/1921425?v=3","gravatar_id":"","url":"https://api.github.com/users/alexispeter","html_url":"https://github.com/alexispeter","followers_url":"https://api.github.com/users/alexispeter/followers","following_url":"https://api.github.com/users/alexispeter/following{/other_user}","gists_url":"https://api.github.com/users/alexispeter/gists{/gist_id}","starred_url":"https://api.github.com/users/alexispeter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alexispeter/subscriptions","organizations_url":"https://api.github.com/users/alexispeter/orgs","repos_url":"https://api.github.com/users/alexispeter/repos","events_url":"https://api.github.com/users/alexispeter/events{/privacy}","received_events_url":"https://api.github.com/users/alexispeter/received_events","type":"User","site_admin":false},"repo":{"id":7134725,"name":"CM","full_name":"alexispeter/CM","owner":{"login":"alexispeter","id":1921425,"avatar_url":"https://avatars.githubusercontent.com/u/1921425?v=3","gravatar_id":"","url":"https://api.github.com/users/alexispeter","html_url":"https://github.com/alexispeter","followers_url":"https://api.github.com/users/alexispeter/followers","following_url":"https://api.github.com/users/alexispeter/following{/other_user}","gists_url":"https://api.github.com/users/alexispeter/gists{/gist_id}","starred_url":"https://api.github.com/users/alexispeter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alexispeter/subscriptions","organizations_url":"https://api.github.com/users/alexispeter/orgs","repos_url":"https://api.github.com/users/alexispeter/repos","events_url":"https://api.github.com/users/alexispeter/events{/privacy}","received_events_url":"https://api.github.com/users/alexispeter/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/alexispeter/CM","description":"","fork":true,"url":"https://api.github.com/repos/alexispeter/CM","forks_url":"https://api.github.com/repos/alexispeter/CM/forks","keys_url":"https://api.github.com/repos/alexispeter/CM/keys{/key_id}","collaborators_url":"https://api.github.com/repos/alexispeter/CM/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/alexispeter/CM/teams","hooks_url":"https://api.github.com/repos/alexispeter/CM/hooks","issue_events_url":"https://api.github.com/repos/alexispeter/CM/issues/events{/number}","events_url":"https://api.github.com/repos/alexispeter/CM/events","assignees_url":"https://api.github.com/repos/alexispeter/CM/assignees{/user}","branches_url":"https://api.github.com/repos/alexispeter/CM/branches{/branch}","tags_url":"https://api.github.com/repos/alexispeter/CM/tags","blobs_url":"https://api.github.com/repos/alexispeter/CM/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/alexispeter/CM/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/alexispeter/CM/git/refs{/sha}","trees_url":"https://api.github.com/repos/alexispeter/CM/git/trees{/sha}","statuses_url":"https://api.github.com/repos/alexispeter/CM/statuses/{sha}","languages_url":"https://api.github.com/repos/alexispeter/CM/languages","stargazers_url":"https://api.github.com/repos/alexispeter/CM/stargazers","contributors_url":"https://api.github.com/repos/alexispeter/CM/contributors","subscribers_url":"https://api.github.com/repos/alexispeter/CM/subscribers","subscription_url":"https://api.github.com/repos/alexispeter/CM/subscription","commits_url":"https://api.github.com/repos/alexispeter/CM/commits{/sha}","git_commits_url":"https://api.github.com/repos/alexispeter/CM/git/commits{/sha}","comments_url":"https://api.github.com/repos/alexispeter/CM/comments{/number}","issue_comment_url":"https://api.github.com/repos/alexispeter/CM/issues/comments{/number}","contents_url":"https://api.github.com/repos/alexispeter/CM/contents/{+path}","compare_url":"https://api.github.com/repos/alexispeter/CM/compare/{base}...{head}","merges_url":"https://api.github.com/repos/alexispeter/CM/merges","archive_url":"https://api.github.com/repos/alexispeter/CM/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/alexispeter/CM/downloads","issues_url":"https://api.github.com/repos/alexispeter/CM/issues{/number}","pulls_url":"https://api.github.com/repos/alexispeter/CM/pulls{/number}","milestones_url":"https://api.github.com/repos/alexispeter/CM/milestones{/number}","notifications_url":"https://api.github.com/repos/alexispeter/CM/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/alexispeter/CM/labels{/name}","releases_url":"https://api.github.com/repos/alexispeter/CM/releases{/id}","created_at":"2012-12-12T17:42:30Z","updated_at":"2014-09-02T10:27:11Z","pushed_at":"2015-06-25T13:14:38Z","git_url":"git://github.com/alexispeter/CM.git","ssh_url":"git@github.com:alexispeter/CM.git","clone_url":"https://github.com/alexispeter/CM.git","svn_url":"https://github.com/alexispeter/CM","homepage":null,"size":29595,"stargazers_count":0,"watchers_count":0,"language":"PHP","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"cargomedia:master","ref":"master","sha":"9413e161adbe1e7d85f085b907af929cf0b16831","user":{"login":"cargomedia","id":1699929,"avatar_url":"https://avatars.githubusercontent.com/u/1699929?v=3","gravatar_id":"","url":"https://api.github.com/users/cargomedia","html_url":"https://github.com/cargomedia","followers_url":"https://api.github.com/users/cargomedia/followers","following_url":"https://api.github.com/users/cargomedia/following{/other_user}","gists_url":"https://api.github.com/users/cargomedia/gists{/gist_id}","starred_url":"https://api.github.com/users/cargomedia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cargomedia/subscriptions","organizations_url":"https://api.github.com/users/cargomedia/orgs","repos_url":"https://api.github.com/users/cargomedia/repos","events_url":"https://api.github.com/users/cargomedia/events{/privacy}","received_events_url":"https://api.github.com/users/cargomedia/received_events","type":"Organization","site_admin":false},"repo":{"id":7134007,"name":"CM","full_name":"cargomedia/CM","owner":{"login":"cargomedia","id":1699929,"avatar_url":"https://avatars.githubusercontent.com/u/1699929?v=3","gravatar_id":"","url":"https://api.github.com/users/cargomedia","html_url":"https://github.com/cargomedia","followers_url":"https://api.github.com/users/cargomedia/followers","following_url":"https://api.github.com/users/cargomedia/following{/other_user}","gists_url":"https://api.github.com/users/cargomedia/gists{/gist_id}","starred_url":"https://api.github.com/users/cargomedia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cargomedia/subscriptions","organizations_url":"https://api.github.com/users/cargomedia/orgs","repos_url":"https://api.github.com/users/cargomedia/repos","events_url":"https://api.github.com/users/cargomedia/events{/privacy}","received_events_url":"https://api.github.com/users/cargomedia/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/cargomedia/CM","description":"CM web application framework","fork":false,"url":"https://api.github.com/repos/cargomedia/CM","forks_url":"https://api.github.com/repos/cargomedia/CM/forks","keys_url":"https://api.github.com/repos/cargomedia/CM/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cargomedia/CM/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cargomedia/CM/teams","hooks_url":"https://api.github.com/repos/cargomedia/CM/hooks","issue_events_url":"https://api.github.com/repos/cargomedia/CM/issues/events{/number}","events_url":"https://api.github.com/repos/cargomedia/CM/events","assignees_url":"https://api.github.com/repos/cargomedia/CM/assignees{/user}","branches_url":"https://api.github.com/repos/cargomedia/CM/branches{/branch}","tags_url":"https://api.github.com/repos/cargomedia/CM/tags","blobs_url":"https://api.github.com/repos/cargomedia/CM/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cargomedia/CM/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cargomedia/CM/git/refs{/sha}","trees_url":"https://api.github.com/repos/cargomedia/CM/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cargomedia/CM/statuses/{sha}","languages_url":"https://api.github.com/repos/cargomedia/CM/languages","stargazers_url":"https://api.github.com/repos/cargomedia/CM/stargazers","contributors_url":"https://api.github.com/repos/cargomedia/CM/contributors","subscribers_url":"https://api.github.com/repos/cargomedia/CM/subscribers","subscription_url":"https://api.github.com/repos/cargomedia/CM/subscription","commits_url":"https://api.github.com/repos/cargomedia/CM/commits{/sha}","git_commits_url":"https://api.github.com/repos/cargomedia/CM/git/commits{/sha}","comments_url":"https://api.github.com/repos/cargomedia/CM/comments{/number}","issue_comment_url":"https://api.github.com/repos/cargomedia/CM/issues/comments{/number}","contents_url":"https://api.github.com/repos/cargomedia/CM/contents/{+path}","compare_url":"https://api.github.com/repos/cargomedia/CM/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cargomedia/CM/merges","archive_url":"https://api.github.com/repos/cargomedia/CM/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cargomedia/CM/downloads","issues_url":"https://api.github.com/repos/cargomedia/CM/issues{/number}","pulls_url":"https://api.github.com/repos/cargomedia/CM/pulls{/number}","milestones_url":"https://api.github.com/repos/cargomedia/CM/milestones{/number}","notifications_url":"https://api.github.com/repos/cargomedia/CM/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cargomedia/CM/labels{/name}","releases_url":"https://api.github.com/repos/cargomedia/CM/releases{/id}","created_at":"2012-12-12T17:00:30Z","updated_at":"2015-05-07T11:04:07Z","pushed_at":"2015-06-25T13:14:39Z","git_url":"git://github.com/cargomedia/CM.git","ssh_url":"git@github.com:cargomedia/CM.git","clone_url":"https://github.com/cargomedia/CM.git","svn_url":"https://github.com/cargomedia/CM","homepage":"","size":37730,"stargazers_count":7,"watchers_count":7,"language":"PHP","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":15,"mirror_url":null,"open_issues_count":36,"forks":15,"open_issues":36,"watchers":7,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/cargomedia/CM/pulls/1667"},"html":{"href":"https://github.com/cargomedia/CM/pull/1667"},"issue":{"href":"https://api.github.com/repos/cargomedia/CM/issues/1667"},"comments":{"href":"https://api.github.com/repos/cargomedia/CM/issues/1667/comments"},"review_comments":{"href":"https://api.github.com/repos/cargomedia/CM/pulls/1667/comments"},"review_comment":{"href":"https://api.github.com/repos/cargomedia/CM/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/cargomedia/CM/pulls/1667/commits"},"statuses":{"href":"https://api.github.com/repos/cargomedia/CM/statuses/634164879bdd4048ec20cea7b292937281027ab9"}}}}