├── docs ├── webpshop_enc_ui_mac.webp ├── webpshop_enc_ui_windows.webp ├── MAINTENANCE.md └── NEWS.md ├── mac ├── Info.plist ├── WebPShopUIUtils_mac.h ├── WebPShopUIUtils_mac.mm ├── WebPShopUIDialog_mac.h ├── WebPShopUI_mac.mm └── WebPShopUIDialog_mac.mm ├── win ├── WebPShopUIResource_windows.h ├── WebPShop.sln ├── WebPShop.rc ├── WebPShop.vcxproj.filters └── WebPShopUI_windows.cpp ├── common ├── WebPShopSelectorFilterFile.cpp ├── WebPShopTerminology.h ├── WebPShopSelectorWriteLayer.cpp ├── WebPShopUtils.cpp ├── WebPShopSelectorEstimate.cpp ├── WebPShopDimensionsUtils.cpp ├── WebPShopUIUtils.cpp ├── WebPShopDecodeAnimUtils.cpp ├── WebPShopSelectorWrite.cpp ├── WebPShopSelectorOptions.cpp ├── WebPShopDataUtils.cpp ├── WebPShopSelectorReadLayer.cpp ├── WebPShopEncodeAnimUtils.cpp ├── WebPShopSelector.h ├── WebPShopSelectorRead.cpp ├── WebPShopScripting.cpp ├── WebPShopDecodeUtils.cpp ├── WebPShopUI.h ├── WebPShopImageUtils.cpp ├── WebPShop.r ├── WebPShop.cpp ├── WebPShopEncodeUtils.cpp ├── WebPShopCanvasUtils.cpp └── WebPShop.h ├── CONTRIBUTING.md ├── README.md └── LICENSE /docs/webpshop_enc_ui_mac.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmproject/WebPShop/HEAD/docs/webpshop_enc_ui_mac.webp -------------------------------------------------------------------------------- /docs/webpshop_enc_ui_windows.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmproject/WebPShop/HEAD/docs/webpshop_enc_ui_windows.webp -------------------------------------------------------------------------------- /mac/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleDisplayName 8 | WebPShop 9 | CFBundleExecutable 10 | $(PRODUCT_NAME) 11 | CFBundleGetInfoString 12 | Copyright 2018-2020 Google LLC 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | 8BIF 19 | CFBundleShortVersionString 20 | 0.4.3 21 | CFBundleSignature 22 | 8BIM 23 | CFBundleVersion 24 | 0.4.3 25 | NSHumanReadableCopyright 26 | Copyright 2018-2020 Google LLC 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/MAINTENANCE.md: -------------------------------------------------------------------------------- 1 | # Maintenance 2 | 3 | Here are described some tips on maintaining the Photoshop plug-in "WebPShop". 4 | 5 | ## Updating WebP version 6 | 7 | To update the WebP **library** version linked in the WebPShop binaries, do the 8 | following: 9 | 10 | * Update the About box text in `win/WebPShop.rc` and 11 | `mac/WebPShopUI_mac.mm` to the new WebP library version. 12 | 13 | * Update the WebP library version in `README.md`. 14 | 15 | * Build WebPShop (Release) for each supported OS/plaform pair and upload. 16 | 17 | ## Updating WebPShop version 18 | 19 | To update the WebPShop **plug-in** version, do the following: 20 | 21 | * Update the About box text in `win/WebPShop.rc` and 22 | `mac/WebPShopUI_mac.mm` to the new WebPShop version and to the current 23 | copyright year (also in `common/WebPShop.r`). 24 | 25 | * Update the WebPShop version in `README.md`, `mac/Info.plist` and 26 | `win/project.pbxproj`. 27 | 28 | * Describe the changes in `docs/NEWS`. 29 | 30 | * Build WebPShop (Release) for each supported OS/plaform pair and upload. 31 | -------------------------------------------------------------------------------- /win/WebPShopUIResource_windows.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //{{NO_DEPENDENCIES}} 16 | // Microsoft Visual C++ generated include file. 17 | // Used by WebPShop.rc 18 | // 19 | 20 | // Next default values for new objects 21 | // 22 | #ifdef APSTUDIO_INVOKED 23 | #ifndef APSTUDIO_READONLY_SYMBOLS 24 | #define _APS_NEXT_RESOURCE_VALUE 101 25 | #define _APS_NEXT_COMMAND_VALUE 40001 26 | #define _APS_NEXT_CONTROL_VALUE 1000 27 | #define _APS_NEXT_SYMED_VALUE 101 28 | #endif 29 | #endif 30 | -------------------------------------------------------------------------------- /common/WebPShopSelectorFilterFile.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "PIFormat.h" 16 | #include "WebPShop.h" 17 | #include "WebPShopSelector.h" 18 | 19 | //------------------------------------------------------------------------------ 20 | 21 | void DoFilterFile(FormatRecordPtr format_record, Data* const data, 22 | int16* const result) { 23 | if (!ReadAndCheckHeader(format_record, result, nullptr)) { 24 | *result = formatCannotRead; 25 | return; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google.com/conduct/). 29 | -------------------------------------------------------------------------------- /common/WebPShopTerminology.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef __WebPShopTerminology_H__ 16 | #define __WebPShopTerminology_H__ 17 | 18 | // Used by LoadWriteConfig() and SaveWriteConfig(). 19 | #define keyWriteConfig_quality 'wrtq' 20 | #define keyWriteConfig_compression 'wrtc' 21 | #define keyWriteConfig_keep_exif 'wrte' 22 | #define keyWriteConfig_keep_xmp 'wrtx' 23 | #define keyWriteConfig_keep_color_profile 'wrtp' 24 | #define keyWriteConfig_loop_forever 'wrtl' 25 | #define keyUsePOSIX 'useP' 26 | 27 | // Used by AddComment() and WebPShop.r 28 | #define histResource 'hist' 29 | #define kHistoryEntry 16989 30 | 31 | #endif // __WebPShopTerminology_H__ 32 | -------------------------------------------------------------------------------- /mac/WebPShopUIUtils_mac.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef __WebPShopUIUtils_mac_H__ 16 | #define __WebPShopUIUtils_mac_H__ 17 | 18 | #include 19 | 20 | //------------------------------------------------------------------------------ 21 | // Function used to draw a grey/white checkerboard (transparency background on 22 | // Photoshop). A possible optimization would be to render an image instead. 23 | 24 | void DrawCheckerboard(CGContextRef cg_context, const NSRect& cg_rect, 25 | const NSSize& tile_size); 26 | 27 | //------------------------------------------------------------------------------ 28 | 29 | #endif // __WebPShopUIUtils_mac_H__ 30 | -------------------------------------------------------------------------------- /common/WebPShopSelectorWriteLayer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "PIFormat.h" 16 | #include "WebPShop.h" 17 | #include "WebPShopSelector.h" 18 | 19 | //------------------------------------------------------------------------------ 20 | 21 | void DoWriteLayerStart(FormatRecordPtr format_record, Data* const data, 22 | int16* const result) {} 23 | 24 | //------------------------------------------------------------------------------ 25 | 26 | void DoWriteLayerContinue(FormatRecordPtr format_record, Data* const data, 27 | int16* const result) { 28 | Deallocate(&format_record->data); 29 | } 30 | 31 | //------------------------------------------------------------------------------ 32 | 33 | void DoWriteLayerFinish(FormatRecordPtr format_record, Data* const data, 34 | int16* const result) {} 35 | -------------------------------------------------------------------------------- /win/WebPShop.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.31112.23 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WebPShop", "WebPShop.vcxproj", "{73C59F9A-C645-4EE5-8F00-AB4332011062}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|ARM64 = Debug|ARM64 10 | Debug|x64 = Debug|x64 11 | Release|ARM64 = Release|ARM64 12 | Release|x64 = Release|x64 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {73C59F9A-C645-4EE5-8F00-AB4332011062}.Debug|ARM64.ActiveCfg = Debug|ARM64 16 | {73C59F9A-C645-4EE5-8F00-AB4332011062}.Debug|ARM64.Build.0 = Debug|ARM64 17 | {73C59F9A-C645-4EE5-8F00-AB4332011062}.Debug|x64.ActiveCfg = Debug|x64 18 | {73C59F9A-C645-4EE5-8F00-AB4332011062}.Debug|x64.Build.0 = Debug|x64 19 | {73C59F9A-C645-4EE5-8F00-AB4332011062}.Release|ARM64.ActiveCfg = Release|ARM64 20 | {73C59F9A-C645-4EE5-8F00-AB4332011062}.Release|ARM64.Build.0 = Release|ARM64 21 | {73C59F9A-C645-4EE5-8F00-AB4332011062}.Release|x64.ActiveCfg = Release|x64 22 | {73C59F9A-C645-4EE5-8F00-AB4332011062}.Release|x64.Build.0 = Release|x64 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {9068977F-2C8C-4E3E-A564-04A104848961} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /common/WebPShopUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include "PIUSuites.h" 20 | #include "WebPShop.h" 21 | 22 | //------------------------------------------------------------------------------ 23 | 24 | void AddComment(FormatRecordPtr format_record, Data* const data, 25 | int16* const result) { 26 | time_t ltime; 27 | time(<ime); 28 | 29 | const std::string currentTime = ctime(<ime); 30 | 31 | size_t length = currentTime.size(); 32 | 33 | Handle h = sPSHandle->New((int32)length); 34 | 35 | if (h != NULL) { 36 | Boolean oldLock = FALSE; 37 | Ptr p = NULL; 38 | sPSHandle->SetLock(h, true, &p, &oldLock); 39 | if (p != NULL) { 40 | for (size_t a = 0; a < length; a++) *p++ = currentTime.at(a); 41 | format_record->resourceProcs->addProc(histResource, h); 42 | sPSHandle->SetLock(h, false, &p, &oldLock); 43 | } 44 | sPSHandle->Dispose(h); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/WebPShopSelectorEstimate.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "PIFormat.h" 16 | #include "WebPShop.h" 17 | #include "WebPShopSelector.h" 18 | 19 | //------------------------------------------------------------------------------ 20 | 21 | void DoEstimatePrepare(FormatRecordPtr format_record, Data* const data, 22 | int16* const result) { 23 | format_record->maxData = 0; // The maximum number of bytes Photoshop can free 24 | // up for a plug-in to use. 25 | } 26 | 27 | //------------------------------------------------------------------------------ 28 | 29 | void DoEstimateStart(FormatRecordPtr format_record, Data* const data, 30 | int16* const result) { 31 | if (data->encoded_data.bytes != nullptr) { 32 | // Output size is already known. 33 | format_record->minDataBytes = (int32)data->encoded_data.size; 34 | format_record->maxDataBytes = (int32)data->encoded_data.size; 35 | } else { 36 | // Best and worst case estimation. 37 | int32 width = (format_record->HostSupports32BitCoordinates 38 | ? format_record->imageSize32.h 39 | : format_record->imageSize.h); 40 | int32 height = (format_record->HostSupports32BitCoordinates 41 | ? format_record->imageSize32.v 42 | : format_record->imageSize.v); 43 | 44 | format_record->minDataBytes = 20; 45 | format_record->maxDataBytes = 46 | width * height * format_record->planes * format_record->planeBytes; 47 | 48 | if (data->write_config.animation) { 49 | format_record->maxDataBytes *= format_record->layerData; 50 | } 51 | 52 | // Maximum overhead compared to raw. 53 | format_record->maxDataBytes += 200; 54 | } 55 | 56 | format_record->data = nullptr; 57 | } 58 | 59 | //------------------------------------------------------------------------------ 60 | 61 | void DoEstimateContinue(FormatRecordPtr format_record, Data* const data, 62 | int16* const result) {} 63 | 64 | //------------------------------------------------------------------------------ 65 | 66 | void DoEstimateFinish(FormatRecordPtr format_record, Data* const data, 67 | int16* const result) {} 68 | -------------------------------------------------------------------------------- /common/WebPShopDimensionsUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "PITypes.h" 16 | #include "WebPShop.h" 17 | 18 | int32 GetWidth(const VRect& rect) { return rect.right - rect.left; } 19 | int32 GetHeight(const VRect& rect) { return rect.bottom - rect.top; } 20 | 21 | //------------------------------------------------------------------------------ 22 | 23 | bool ScaleToFit(int32* const width, int32* const height, int32 max_width, 24 | int32 max_height) { 25 | if (width == nullptr || height == nullptr) { 26 | LOG("/!\\ Width or height is null."); 27 | return false; 28 | } 29 | if (*width <= 0 || *height <= 0 || max_width <= 0 || max_height <= 0) { 30 | LOG("/!\\ Invalid input."); 31 | return false; 32 | } 33 | if (*width > max_width || *height > max_height) { 34 | const int32 original_width = *width; 35 | const int32 original_height = *height; 36 | // Try resizing based on width first to see if it fits. 37 | *width = max_width; 38 | *height = (original_height * max_width) / original_width; 39 | *height = (*height < 1) ? 1 : *height; 40 | // If it doesn't, resize based on height. 41 | if (*height > max_height) { 42 | *width = (original_width * max_height) / original_height; 43 | *width = (*width < 1) ? 1 : *width; 44 | *height = max_height; 45 | } 46 | return true; 47 | } 48 | return false; 49 | } 50 | 51 | bool CropToFit(int32* const width, int32* const height, int32 left, int32 top, 52 | int32 max_width, int32 max_height) { 53 | if (width == nullptr || height == nullptr) { 54 | LOG("/!\\ Width or height is null."); 55 | return false; 56 | } 57 | if (*width <= 0 || *height <= 0 || left < 0 || top < 0 || left >= *width || 58 | top >= *height || max_width <= 0 || max_height <= 0) { 59 | LOG("/!\\ Invalid input."); 60 | return false; 61 | } 62 | if (left == 0 && top == 0 && *width <= max_width && *height <= max_height) { 63 | return false; 64 | } 65 | *width -= left; 66 | *height -= top; 67 | if (*width > max_width) *width = max_width; 68 | if (*height > max_height) *height = max_height; 69 | return true; 70 | } 71 | 72 | VRect ScaleRectFromAreaToArea(const VRect& src, int32 src_area_width, 73 | int32 src_area_height, int32 dst_area_width, 74 | int32 dst_area_height) { 75 | VRect dst; 76 | dst.left = (src.left * dst_area_width) / src_area_width; 77 | dst.right = (src.right * dst_area_width) / src_area_width; 78 | dst.top = (src.top * dst_area_height) / src_area_height; 79 | dst.bottom = (src.bottom * dst_area_height) / src_area_height; 80 | return dst; 81 | } 82 | -------------------------------------------------------------------------------- /mac/WebPShopUIUtils_mac.mm: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "WebPShopUIUtils_mac.h" 16 | 17 | #include 18 | #include 19 | 20 | //------------------------------------------------------------------------------ 21 | 22 | static void DrawTile(CGContextRef cg_context, const NSRect& cg_rect, 23 | CGFloat x, CGFloat y, const NSSize& tile_size) { 24 | const CGFloat right = std::min(x + tile_size.width, cg_rect.size.width); 25 | const CGFloat bottom = std::min(y + tile_size.height, cg_rect.size.height); 26 | NSRect tile_rect = NSMakeRect(x, y, right - x, bottom - y); 27 | tile_rect.origin.y = 28 | cg_rect.size.height - tile_rect.origin.y - tile_rect.size.height; 29 | tile_rect.origin.x += cg_rect.origin.x; 30 | tile_rect.origin.y += cg_rect.origin.y; 31 | CGContextFillRect(cg_context, tile_rect); 32 | } 33 | 34 | void DrawCheckerboard(CGContextRef cg_context, const NSRect& cg_rect, 35 | const NSSize& tile_size) { 36 | CGColorRef white = CGColorCreateGenericRGB(1.0, 1.0, 1.0, 1.0); 37 | CGContextSetFillColorWithColor(cg_context, white); 38 | // Draw all tiles of even rows, even columns in white. 39 | for (CGFloat x = 0; x < cg_rect.size.width; x += 2 * tile_size.width) { 40 | for (CGFloat y = 0; y < cg_rect.size.height; y += 2 * tile_size.height) { 41 | DrawTile(cg_context, cg_rect, x, y, tile_size); 42 | } 43 | } 44 | // Draw all tiles of uneven rows, uneven columns in white. 45 | for (CGFloat x = tile_size.width; x < cg_rect.size.width; 46 | x += 2 * tile_size.width) { 47 | for (CGFloat y = tile_size.height; y < cg_rect.size.height; 48 | y += 2 * tile_size.height) { 49 | DrawTile(cg_context, cg_rect, x, y, tile_size); 50 | } 51 | } 52 | CGColorRelease(white); 53 | 54 | CGColorRef grey = CGColorCreateGenericRGB(0.8, 0.8, 0.8, 1.0); 55 | CGContextSetFillColorWithColor(cg_context, grey); 56 | // Draw all tiles of even rows, uneven columns in grey. 57 | for (CGFloat x = 0; x < cg_rect.size.width; x += 2 * tile_size.width) { 58 | for (CGFloat y = tile_size.height; y < cg_rect.size.height; 59 | y += 2 * tile_size.height) { 60 | DrawTile(cg_context, cg_rect, x, y, tile_size); 61 | } 62 | } 63 | // Draw all tiles of uneven rows, even columns in grey. 64 | for (CGFloat x = tile_size.width; x < cg_rect.size.width; 65 | x += 2 * tile_size.width) { 66 | for (CGFloat y = 0; y < cg_rect.size.height; y += 2 * tile_size.height) { 67 | DrawTile(cg_context, cg_rect, x, y, tile_size); 68 | } 69 | } 70 | CGColorRelease(grey); 71 | } 72 | 73 | //------------------------------------------------------------------------------ 74 | -------------------------------------------------------------------------------- /common/WebPShopUIUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include "PIFormat.h" 18 | #include "WebPShop.h" 19 | 20 | //------------------------------------------------------------------------------ 21 | 22 | VRect NullRect() { 23 | VRect rect; 24 | rect.left = rect.right = rect.top = rect.bottom = 0; 25 | return rect; 26 | } 27 | VRect GetCenteredRectInArea(const VRect& area, int32 width, int32 height) { 28 | VRect rect; 29 | rect.left = (area.left + area.right) / 2 - width / 2; 30 | rect.right = rect.left + width; 31 | rect.top = (area.top + area.bottom) / 2 - height / 2; 32 | rect.bottom = rect.top + height; 33 | return rect; 34 | } 35 | 36 | VRect GetCropAreaRectInWindow(const VRect& proxy_area_rect_in_window) { 37 | VRect crop_rect = proxy_area_rect_in_window; 38 | crop_rect.left = crop_rect.right - (crop_rect.bottom - crop_rect.top); 39 | crop_rect.left += 2; // Leaving room for the selection borders. 40 | crop_rect.right -= 2; 41 | crop_rect.top += 2; 42 | crop_rect.bottom -= 2; 43 | return crop_rect; 44 | } 45 | VRect GetScaleAreaRectInWindow(const VRect& proxy_area_rect_in_window) { 46 | VRect scale_rect = proxy_area_rect_in_window; 47 | scale_rect.right = 48 | GetCropAreaRectInWindow(proxy_area_rect_in_window).left - 5; 49 | scale_rect.left += 2; // Leaving room for the selection borders. 50 | scale_rect.right -= 2; 51 | scale_rect.top += 2; 52 | scale_rect.bottom -= 2; 53 | return scale_rect; 54 | } 55 | 56 | //------------------------------------------------------------------------------ 57 | 58 | std::string DataSizeToString(size_t data_size) { 59 | if (data_size < 1024) { 60 | return std::to_string(data_size) + " B"; 61 | } 62 | if (data_size < 1024 * 1024) { 63 | return std::to_string(data_size / 1024) + "." + 64 | std::to_string((data_size % 1024) * 1000 / 1024 / 100) + " KB"; 65 | } 66 | return std::to_string(data_size / (1024 * 1024)) + "." + 67 | std::to_string((data_size % (1024 * 1024)) * 1000 / (1024 * 1024) / 68 | 100) + 69 | " MB"; 70 | } 71 | 72 | void SetErrorString(FormatRecordPtr format_record, const std::string& str) { 73 | if (format_record->errorString != nullptr && str.length() < 255) { 74 | Str255& errorString = *format_record->errorString; 75 | errorString[0] = static_cast(str.length()); 76 | std::copy(str.c_str(), str.c_str() + str.length() + 1, // Include '\0'. 77 | reinterpret_cast(errorString + 1)); 78 | LOG("errorString: " << str); 79 | } else { 80 | LOG("errorString (not set): " << str); 81 | } 82 | } 83 | 84 | //------------------------------------------------------------------------------ 85 | -------------------------------------------------------------------------------- /mac/WebPShopUIDialog_mac.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef __WebPShopUIDialog_mac_H__ 16 | #define __WebPShopUIDialog_mac_H__ 17 | 18 | #include 19 | #include 20 | #include "AppKit/NSWindow.h" 21 | #include "PIUI.h" 22 | 23 | #include "WebPShop.h" 24 | #include "WebPShopUI.h" 25 | 26 | //------------------------------------------------------------------------------ 27 | // WebPShopProxyView is the UI element containing the preview area in the 28 | // encoding settings window. It inherits NSView for preview image drawing and 29 | // events. 30 | 31 | @interface WebPShopProxyView : NSView 32 | @property(assign) WebPShopDialog *dialog; 33 | - (NSRect)convertTopLeftRectToCGContext:(NSRect)rect; 34 | @end 35 | 36 | //------------------------------------------------------------------------------ 37 | // WebPShopDelegate handles callbacks sent by UI elements. 38 | 39 | @interface WebPShopDelegate : NSObject 40 | @property(assign) NSWindow *window; 41 | @property(assign) WebPShopDialog *dialog; 42 | - (void)clickOk:(id)sender; 43 | - (void)clickCancel:(id)sender; 44 | - (void)notified:(NSObject *)sender; 45 | @end 46 | 47 | //------------------------------------------------------------------------------ 48 | // Contains each UI element with its matching id. 49 | 50 | struct Dialog { 51 | public: 52 | void Set(short item_id, void* const item_ptr); 53 | PIItem GetItemPtr(short item_id); 54 | short GetItemId(PIItem item_ptr); 55 | NSWindow* CreateWindow(WebPShopDelegate* const delegate); 56 | 57 | NSTextField* settings_text = nullptr; 58 | NSButton* ok_button = nullptr; 59 | NSButton* cancel_button = nullptr; 60 | int clicked_button = kDCancel; 61 | 62 | NSBox* quality_box = nullptr; 63 | NSSlider* quality_slider = nullptr; 64 | NSTextField* quality_field = nullptr; 65 | NSTextField* quality_text_smallest = nullptr; 66 | NSTextField* quality_text_lossless = nullptr; 67 | 68 | NSBox* compression_box = nullptr; 69 | NSButton* compression_radio_button_fastest = nullptr; 70 | NSButton* compression_radio_button_default = nullptr; 71 | NSButton* compression_radio_button_smallest = nullptr; 72 | 73 | NSBox* metadata_box = nullptr; 74 | NSButton* metadata_exif_checkbox = nullptr; 75 | NSButton* metadata_xmp_checkbox = nullptr; 76 | NSButton* metadata_iccp_checkbox = nullptr; 77 | NSButton* metadata_loop_checkbox = nullptr; 78 | 79 | NSBox* proxy_box = nullptr; 80 | NSButton* proxy_checkbox = nullptr; 81 | WebPShopProxyView* proxy_view = nullptr; 82 | 83 | NSTextField* frame_text = nullptr; 84 | NSSlider* frame_index_slider = nullptr; 85 | NSTextField* frame_index_field = nullptr; 86 | NSTextField* frame_duration_text = nullptr; 87 | 88 | private: 89 | std::map item_id_to_ptr; 90 | }; 91 | 92 | //------------------------------------------------------------------------------ 93 | 94 | #endif // __WebPShopUIDialog_mac_H__ 95 | -------------------------------------------------------------------------------- /docs/NEWS.md: -------------------------------------------------------------------------------- 1 | - 2018/10/31: v0.1.0 2 | 3 | * Initial release of WebPShop, a Photoshop plug-in for reading and writing 4 | WebP files. 5 | * Handles animations too. 6 | 7 | - 2019/03/01: v0.1.1 8 | 9 | * Updated to Adobe Photoshop Plug-In and Connection SDK CC 2019 10 | (posix i/o for Mac, no handle lock). 11 | * Updated to libwebp 1.0.2. 12 | * Tested on Windows Server 2016 Datacenter with Photoshop CC 2018 13 | (v 19.1.7) x64, CC 2019 (v.20.0.3) x64, Release and Debug. 14 | 15 | - 2019/03/15: v0.2.0 16 | 17 | * Now compiles with XCode. 18 | * Added Cocoa interface implementation for Mac (encoding settings window 19 | and About box). 20 | * Tested on a MacBook Pro (macOS Mojave 10.14.3) with Photoshop CC 2019 21 | (v 20.0.3 and 20.0.4). 22 | 23 | - 2019/04/24: v0.2.1 24 | 25 | * Adapted UI quality and compression mappings to WebP encoding settings. 26 | * Tested on Windows Server 2016 Datacenter with Photoshop CC 2019 27 | (v 20.0.4) x64, Release. 28 | 29 | - 2020/04/29: v0.3.0 30 | 31 | * Added the "Loop forever" checkbox for animations. 32 | * Added metadata import/export from/to WebP through the "Keep EXIF", 33 | "Keep XMP" and "Keep Color Profile" checkboxes. 34 | * Tested on Windows Server 2016 Datacenter with Photoshop 2020 35 | (v 21.1.2) x64, Release. 36 | 37 | - 2020/06/26: v0.3.1 38 | 39 | * Fix inaccuracies in WebPShop.r resource file. 40 | * Fix PIUtilities.r MacOS ResMerger warning. 41 | * Add directions in README.md. 42 | 43 | - 2021/02/05: v0.3.2 44 | 45 | * Updated to Adobe Photoshop Plug-In and Connection SDK 2021 v2 (2021). 46 | * Updated to libwebp 1.2.0. 47 | * Built with Microsoft Visual Studio Community 2017 15.8.3 and 48 | Apple XCode 12.4 (12D4e). 49 | * Tested on Windows Server 2016 Datacenter with Photoshop 2021 50 | (v 22.1.1) x64, Release. 51 | * Tested on macOS Catalina Version 10.15.7 with Photoshop 2020 52 | (v 21.2.4) and Photoshop 2021 (v 22.1.1) x64, Release. 53 | 54 | - 2021/04/09: v0.3.3 55 | 56 | * Built with Microsoft Visual Studio Community 2019 16.9.2. 57 | * Removed the Windows x86 architecture target. 58 | * Added the Windows ARM64 architecture target. 59 | * Tested on Windows Server 2016 Datacenter with Photoshop 2021 60 | (v 22.3.0) x64, Release. 61 | 62 | - 2021/06/29: v0.3.4 63 | 64 | * Fixed the opening of animated WebP images as Smart Objects. 65 | 66 | - 2021/09/14: v0.4.0 67 | 68 | * Updated to libwebp 1.2.1. 69 | * Added 16 and 32 bits/channel support. 70 | * Display an error prompt instead of hiding WebP export when 71 | saving an image of another Mode than RGB Color. 72 | * Built with Microsoft Visual Studio Community 2019 16.11.0. 73 | * Tested on Windows Server 2016 Datacenter with Photoshop 2021 74 | (v 22.5.1) x64, Release. 75 | 76 | - 2021/11/26: v0.4.1 77 | 78 | * Fix focus issue when closing the WebP encoding settings window on 79 | Windows. 80 | * Built with Microsoft Visual Studio Community 2019 16.11.7. 81 | * Tested on Windows Server 2016 Datacenter with Photoshop 2021 82 | (v 23.0.2) x64, Release. 83 | 84 | - 2022/02/21: v0.4.2 85 | 86 | * Updated to Adobe Photoshop Plug-In and Connection SDK 2021 v1 (2022). 87 | * Updated to libwebp 1.2.2. 88 | * Added a close button to the About WebP dialog. 89 | * Increased the Encoding Settings dialog dimensions (bigger preview). 90 | * Built with Microsoft Visual Studio Community 2019 16.11.10. 91 | * Tested on Windows Server 2016 Datacenter with Photoshop 2022 92 | (v 23.2.0) x64, Release. 93 | 94 | - 2022/04/01: v0.4.3 95 | 96 | * Fixed the Preview applying the color profile with no ICC on Windows. 97 | * Built with Microsoft Visual Studio Community 2019 16.11.10. 98 | * Tested on Windows Server 2016 Datacenter with Photoshop 2022 99 | (v 23.2.2) x64, Release. 100 | -------------------------------------------------------------------------------- /common/WebPShopDecodeAnimUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include "WebPShop.h" 18 | #include "webp/demux.h" 19 | 20 | void PrintDuration(int frame_index, int frame_duration, uint16 output[], 21 | size_t output_max_length) { 22 | std::string layer_name = "Frame " + std::to_string(frame_index + 1) + " (" + 23 | std::to_string(frame_duration) + " ms)"; 24 | layer_name.push_back('\0'); 25 | if (output == nullptr || output_max_length < layer_name.size()) { 26 | LOG("/!\\ output is null or too small."); 27 | return; 28 | } 29 | std::copy(layer_name.begin(), layer_name.end(), output); 30 | } 31 | 32 | //------------------------------------------------------------------------------ 33 | 34 | bool DecodeAllFrames(const WebPData& encoded_data, 35 | std::vector* const compressed_frames) { 36 | START_TIMER(DecodeAllFrames); 37 | 38 | if (encoded_data.bytes == nullptr || compressed_frames == nullptr) { 39 | LOG("/!\\ Source or destination is null."); 40 | return false; 41 | } 42 | 43 | WebPAnimDecoderOptions options; 44 | if (!WebPAnimDecoderOptionsInit(&options)) { 45 | LOG("/!\\ WebPAnimDecoderOptionsInit() failed."); 46 | return false; 47 | } 48 | options.color_mode = MODE_RGBA; 49 | 50 | WebPAnimDecoder* const anim_decoder = 51 | WebPAnimDecoderNew(&encoded_data, &options); 52 | if (anim_decoder == nullptr) { 53 | LOG("/!\\ WebPAnimDecoderNew() failed."); 54 | return false; 55 | } 56 | 57 | WebPAnimInfo info; 58 | if (!WebPAnimDecoderGetInfo(anim_decoder, &info)) { 59 | LOG("/!\\ WebPAnimDecoderGetInfo() failed."); 60 | WebPAnimDecoderDelete(anim_decoder); 61 | return false; 62 | } 63 | 64 | if (info.frame_count > MAX_NUM_BROWSED_LAYERS) { 65 | LOG("/!\\ Too many layers."); 66 | WebPAnimDecoderDelete(anim_decoder); 67 | return false; 68 | } 69 | 70 | ResizeFrameVector(compressed_frames, info.frame_count); 71 | size_t frame_counter = 0; 72 | int last_frame_timestamp_ms = 0; 73 | while (WebPAnimDecoderHasMoreFrames(anim_decoder) && 74 | frame_counter < compressed_frames->size()) { 75 | uint8_t* buf; 76 | int timestamp; 77 | if (!WebPAnimDecoderGetNext(anim_decoder, &buf, ×tamp)) { 78 | LOG("/!\\ WebPAnimDecoderGetNext() failed."); 79 | WebPAnimDecoderDelete(anim_decoder); 80 | return false; 81 | } 82 | 83 | FrameMemoryDesc& compressed_frame = (*compressed_frames)[frame_counter]; 84 | compressed_frame.duration_ms = timestamp - last_frame_timestamp_ms; 85 | 86 | if (!AllocateImage(&compressed_frame.image, (int32)info.canvas_width, 87 | (int32)info.canvas_height, /*num_channels=*/4, 88 | /*bit_depth=*/8)) { 89 | LOG("/!\\ AllocateImage failed."); 90 | WebPAnimDecoderDelete(anim_decoder); 91 | return false; 92 | } 93 | 94 | // The output of WebPAnimDecoderGetNext() is valid only for a loop. 95 | memcpy(compressed_frame.image.pixels.data, buf, 96 | info.canvas_width * info.canvas_height * 4); 97 | 98 | last_frame_timestamp_ms = timestamp; 99 | ++frame_counter; 100 | } 101 | 102 | WebPAnimDecoderDelete(anim_decoder); 103 | LOG("Decoded " << encoded_data.size << " bytes into " << frame_counter 104 | << " / " << info.frame_count << " frames."); 105 | 106 | STOP_TIMER(DecodeAllFrames); 107 | return (!compressed_frames->empty() && 108 | compressed_frames->size() == frame_counter); 109 | } 110 | -------------------------------------------------------------------------------- /common/WebPShopSelectorWrite.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "PIFormat.h" 16 | #include "WebPShop.h" 17 | #include "WebPShopSelector.h" 18 | 19 | void DoWritePrepare(FormatRecordPtr format_record, Data* const data, 20 | int16* const result) { 21 | format_record->maxData = 0; 22 | LoadPOSIXConfig(format_record, result); 23 | } 24 | 25 | //------------------------------------------------------------------------------ 26 | 27 | void DoWriteStart(FormatRecordPtr format_record, Data* const data, 28 | int16* const result) { 29 | format_record->planeMap[0] = 2; // BGRA 30 | format_record->planeMap[1] = 1; 31 | format_record->planeMap[2] = 0; 32 | format_record->planeMap[3] = 3; 33 | 34 | SetPlaneColRowBytes(format_record); 35 | 36 | LOG("layerData: " << format_record->layerData); 37 | LOG("loPlane: " << format_record->loPlane); 38 | LOG("hiPlane: " << format_record->hiPlane); 39 | 40 | LOG("planeBytes: " << format_record->planeBytes); 41 | LOG("colBytes: " << format_record->colBytes); 42 | LOG("rowBytes: " << format_record->rowBytes); 43 | 44 | if (data->encoded_data.bytes == nullptr) { 45 | // We could use format_record->data and DoWrite*() but we'd have to 46 | // handle RGB. Copy*() always return RGBA, which is needed for WebPEncode(). 47 | if (data->write_config.animation) { 48 | std::vector original_frames; 49 | CopyAllLayers(format_record, data, result, &original_frames); 50 | 51 | if (*result == noErr && 52 | (!EncodeAllFrames(original_frames, data->write_config, 53 | &data->encoded_data) || 54 | data->encoded_data.bytes == nullptr || 55 | data->encoded_data.size == 0)) { 56 | *result = writErr; 57 | } 58 | ClearFrameVector(&original_frames); 59 | } else { // !data->write_config.animation 60 | ImageMemoryDesc image; 61 | CopyWholeCanvas(format_record, data, result, &image); 62 | 63 | if (*result == noErr && 64 | (!EncodeOneImage(image, data->write_config, &data->encoded_data) || 65 | data->encoded_data.bytes == nullptr || 66 | data->encoded_data.size == 0)) { 67 | *result = writErr; 68 | } 69 | DeallocateImage(&image); 70 | } 71 | if (*result == noErr && !EncodeMetadata(data->write_config, data->metadata, 72 | &data->encoded_data)) { 73 | *result = writErr; 74 | } 75 | } 76 | RequestWholeCanvas(format_record, result); // Prevent inf loop. 77 | 78 | if (*result != noErr) Deallocate(&format_record->data); 79 | } 80 | 81 | //------------------------------------------------------------------------------ 82 | 83 | void DoWriteContinue(FormatRecordPtr format_record, Data* const data, 84 | int16* const result) { 85 | if (*result == noErr) { 86 | WriteToFile(data->encoded_data, format_record, result); 87 | } 88 | 89 | WebPDataClear(&data->encoded_data); 90 | DeallocateMetadata(data->metadata); 91 | Deallocate(&format_record->data); 92 | } 93 | 94 | //------------------------------------------------------------------------------ 95 | 96 | void DoWriteFinish(FormatRecordPtr format_record, Data* const data, 97 | int16* const result) { 98 | // Should be empty. Just in case. 99 | WebPDataClear(&data->encoded_data); 100 | DeallocateMetadata(data->metadata); 101 | Deallocate(&format_record->data); 102 | 103 | if (*result == noErr) { 104 | SaveWriteConfig(format_record, data->write_config, result); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /common/WebPShopSelectorOptions.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "PIFormat.h" 16 | #include "SPPlugs.h" 17 | #include "WebPShop.h" 18 | #include "WebPShopSelector.h" 19 | #include "WebPShopUI.h" 20 | 21 | static bool IsAnimation(FormatRecordPtr format_record) { 22 | const ReadLayerDesc* layer_desc = 23 | format_record->documentInfo->layersDescriptor; 24 | int num_layers = 0; 25 | while (layer_desc != nullptr && num_layers < MAX_NUM_BROWSED_LAYERS) { 26 | int frame_duration; 27 | if (!TryExtractDuration(layer_desc->unicodeName, &frame_duration)) { 28 | return false; 29 | } 30 | layer_desc = layer_desc->next; 31 | ++num_layers; 32 | } 33 | return (num_layers > 1 && num_layers < MAX_NUM_BROWSED_LAYERS); 34 | } 35 | 36 | //------------------------------------------------------------------------------ 37 | 38 | void DoOptionsPrepare(FormatRecordPtr format_record, Data* const data, 39 | int16* const result) { 40 | format_record->maxData = 0; 41 | 42 | // plugInModeRGB48 and plugInModeRGB96 do not seem to be Image Mode RGB Color 43 | // with 16 or 32 bits/channel. Check 'format_record->depth' instead. 44 | if (format_record->imageMode != plugInModeRGBColor) { 45 | // format_record->convertMode does not seem to work here. 46 | SetErrorString(format_record, "Image > Mode is not RGB Color"); 47 | *result = errReportString; 48 | } 49 | } 50 | 51 | //------------------------------------------------------------------------------ 52 | 53 | void DoOptionsStart(FormatRecordPtr format_record, Data* const data, 54 | int16* const result, const SPPluginRef& plugin_ref) { 55 | LoadWriteConfig(format_record, &data->write_config, result); 56 | data->write_config.animation = IsAnimation(format_record); 57 | 58 | WebPDataClear(&data->encoded_data); 59 | if (*result == noErr) { 60 | *result = GetHostMetadata(format_record, data->metadata); 61 | } 62 | 63 | // Do not ask encoding parameters to the user unless Photoshop requests it. 64 | // This is usually plugInDialogSilent during Batch. 65 | const PIDescriptorParameters* const desc_params = 66 | format_record->descriptorParameters; 67 | const bool display_encoding_parameters = 68 | desc_params == nullptr || desc_params->playInfo == plugInDialogDisplay; 69 | 70 | if (display_encoding_parameters) { 71 | std::vector frames; 72 | if (*result == noErr) { 73 | if (data->write_config.animation) { 74 | CopyAllLayers(format_record, data, result, &frames); 75 | } else { 76 | ResizeFrameVector(&frames, 1); 77 | CopyWholeCanvas(format_record, data, result, &frames[0].image); 78 | } 79 | } 80 | 81 | if (*result == noErr) { 82 | if (!DoUI(&data->write_config, data->metadata, plugin_ref, frames, 83 | /*original_frames_were_converted_to_8b=*/ 84 | (format_record->depth != 8), &data->encoded_data, 85 | format_record->displayPixels)) { 86 | *result = userCanceledErr; 87 | } 88 | } 89 | 90 | if (*result != noErr) WebPDataClear(&data->encoded_data); 91 | ClearFrameVector(&frames); 92 | } 93 | 94 | if (*result != noErr) DeallocateMetadata(data->metadata); 95 | // If noErr but it does not go through DoWriteFinish(), some data may leak. 96 | 97 | format_record->data = nullptr; 98 | } 99 | 100 | //------------------------------------------------------------------------------ 101 | 102 | void DoOptionsContinue(FormatRecordPtr format_record, Data* const data, 103 | int16* const result) {} 104 | 105 | //------------------------------------------------------------------------------ 106 | 107 | void DoOptionsFinish(FormatRecordPtr format_record, Data* const data, 108 | int16* const result) {} 109 | -------------------------------------------------------------------------------- /common/WebPShopDataUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | 18 | #include "FileUtilities.h" 19 | #include "WebPShop.h" 20 | 21 | //------------------------------------------------------------------------------ 22 | 23 | void ReadSome(size_t count, void* const buffer, FormatRecordPtr format_record, 24 | int16* const result) { 25 | if (*result != noErr) return; 26 | int32 read_count = (int32)count; 27 | *result = PSSDKRead((int32)format_record->dataFork, 28 | format_record->posixFileDescriptor, 29 | format_record->pluginUsingPOSIXIO, &read_count, buffer); 30 | if (*result == noErr && (size_t)read_count != count) *result = eofErr; 31 | if (*result != noErr) LOG("/!\\ Unable to read " << count << " bytes."); 32 | } 33 | void WriteSome(size_t count, const void* const buffer, 34 | FormatRecordPtr format_record, int16* const result) { 35 | if (*result != noErr) return; 36 | int32 write_count = (int32)count; 37 | *result = PSSDKWrite( 38 | (int32)format_record->dataFork, format_record->posixFileDescriptor, 39 | format_record->pluginUsingPOSIXIO, &write_count, (void*)buffer); 40 | if (*result == noErr && (size_t)write_count != count) *result = dskFulErr; 41 | if (*result != noErr) LOG("/!\\ Unable to write " << count << " bytes."); 42 | } 43 | 44 | void Allocate(size_t count, void** const buffer, int16* const result) { 45 | if (buffer == nullptr) { 46 | *result = paramErr; 47 | return; 48 | } 49 | unsigned32 buffer_count = (unsigned32)count; 50 | *buffer = sPSBuffer->New(&buffer_count, (unsigned32)count); 51 | if (*buffer == nullptr || buffer_count != (unsigned32)count) { 52 | Deallocate(buffer); 53 | *result = memFullErr; 54 | } 55 | if (*result != noErr) LOG("/!\\ Unable to allocate " << count << " bytes."); 56 | } 57 | void AllocateAndRead(size_t count, void** const buffer, 58 | FormatRecordPtr format_record, int16* const result) { 59 | Allocate(count, buffer, result); 60 | if (*result != noErr) return; 61 | 62 | *result = PSSDKSetFPos((int32)format_record->dataFork, 63 | format_record->posixFileDescriptor, 64 | format_record->pluginUsingPOSIXIO, fsFromStart, 0); 65 | if (*result != noErr) { 66 | LOG("/!\\ Unable to set cursor at the beginning of the file."); 67 | Deallocate(buffer); 68 | return; 69 | } 70 | 71 | ReadSome(count, *buffer, format_record, result); 72 | if (*result != noErr) { 73 | Deallocate(buffer); 74 | return; 75 | } 76 | } 77 | void Deallocate(void** const buffer) { 78 | if (buffer == nullptr) return; 79 | Ptr ptr = (Ptr)*buffer; 80 | *buffer = nullptr; 81 | sPSBuffer->Dispose(&ptr); 82 | } 83 | 84 | //------------------------------------------------------------------------------ 85 | 86 | bool ReadAndCheckHeader(FormatRecordPtr format_record, int16* const result, 87 | size_t* file_size) { 88 | if (*result != noErr) return false; 89 | 90 | *result = PSSDKSetFPos((int32)format_record->dataFork, 91 | format_record->posixFileDescriptor, 92 | format_record->pluginUsingPOSIXIO, fsFromStart, 0); 93 | if (*result != noErr) return false; 94 | 95 | uint8_t file_header[12]; 96 | ReadSome(sizeof(file_header), file_header, format_record, result); 97 | if (*result != noErr) return false; 98 | 99 | if (memcmp(file_header, "RIFF", 4) != 0 || 100 | memcmp(file_header + 8, "WEBP", 4) != 0) { 101 | *result = formatCannotRead; 102 | return false; 103 | } 104 | 105 | if (file_size != nullptr) { 106 | *file_size = ((size_t)(file_header[4] << 0) | (file_header[5] << 8) | 107 | (file_header[6] << 16) | (file_header[7] << 24)) + 108 | 8; 109 | LOG("File size: " << *file_size); 110 | } 111 | return true; 112 | } 113 | -------------------------------------------------------------------------------- /win/WebPShop.rc: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Microsoft Visual C++ generated resource script. 16 | // 17 | #include "WebPShopUIResource_windows.h" 18 | 19 | #define APSTUDIO_READONLY_SYMBOLS 20 | ///////////////////////////////////////////////////////////////////////////// 21 | // 22 | // Generated from the TEXTINCLUDE 2 resource. 23 | // 24 | #include "winres.h" 25 | 26 | ///////////////////////////////////////////////////////////////////////////// 27 | #undef APSTUDIO_READONLY_SYMBOLS 28 | 29 | ///////////////////////////////////////////////////////////////////////////// 30 | // English (United States) resources 31 | 32 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 33 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 34 | #pragma code_page(1252) 35 | 36 | #ifdef APSTUDIO_INVOKED 37 | ///////////////////////////////////////////////////////////////////////////// 38 | // 39 | // TEXTINCLUDE 40 | // 41 | 42 | 1 TEXTINCLUDE 43 | BEGIN 44 | "WebPShopUIResource_windows.h\0" 45 | END 46 | 47 | 2 TEXTINCLUDE 48 | BEGIN 49 | "#include ""winres.h""\r\n" 50 | "\0" 51 | END 52 | 53 | 3 TEXTINCLUDE 54 | BEGIN 55 | "#include ""WebPShop.pipl""\r\n" 56 | "\0" 57 | END 58 | 59 | #endif // APSTUDIO_INVOKED 60 | 61 | 62 | ///////////////////////////////////////////////////////////////////////////// 63 | // 64 | // Dialog 65 | // 66 | 67 | 16090 DIALOGEX 0, 0, 671, 518 68 | STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU 69 | CAPTION "WebPShop" 70 | FONT 8, "MS Shell Dlg", 400, 0, 0x1 71 | BEGIN 72 | DEFPUSHBUTTON "&OK",1,612,6,50,14 73 | PUSHBUTTON "&Cancel",2,612,23,50,14 74 | LTEXT "WebP settings:",5,6,6,250,8 75 | GROUPBOX "Quality",10,106,6,204,54 76 | CONTROL "",11,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,140,18,100,15 77 | EDITTEXT 12,274,18,24,14,ES_CENTER | ES_AUTOHSCROLL 78 | LTEXT "Lossy",13,135,38,50,8 79 | LTEXT "Lossless",14,220,38,50,8 80 | GROUPBOX "Compression",20,322,6,78,54 81 | CONTROL "Fastest",21,"Button",BS_AUTORADIOBUTTON,328,18,66,10 82 | CONTROL "Default",22,"Button",BS_AUTORADIOBUTTON,328,30,66,10 83 | CONTROL "Slowest",23,"Button",BS_AUTORADIOBUTTON,328,42,66,10 84 | GROUPBOX "Metadata",30,412,6,188,54 85 | CONTROL "Keep EXIF",33,"Button",BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,418,18,80,10 86 | CONTROL "Keep XMP",34,"Button",BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,418,30,80,10 87 | CONTROL "Keep Color Profile",35,"Button",BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,418,42,80,10 88 | CONTROL "Loop forever",38,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,508,18,80,10 89 | GROUPBOX " ",40,6,74,659,440 90 | CTEXT "",41,10,88,650,420,NOT WS_VISIBLE 91 | CONTROL "Preview: 12.3 kB",42,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,73,80,8 92 | RTEXT " Frame: ",45,446,73,49,8 93 | CONTROL "",46,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,495,72,100,12 94 | EDITTEXT 47,595,70,24,14,ES_CENTER | ES_AUTOHSCROLL 95 | RTEXT "40 ms",48,619,73,35,8 96 | END 97 | 98 | 16091 DIALOG 0, 0, 200, 90 99 | STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU 100 | CAPTION "About WebPShop" 101 | FONT 8, "MS Shell Dlg" 102 | BEGIN 103 | DEFPUSHBUTTON "OK",1,171,7,11,14,NOT WS_VISIBLE | WS_DISABLED 104 | PUSHBUTTON "Cancel",2,172,24,10,14,NOT WS_VISIBLE | WS_DISABLED 105 | LTEXT "WebPShop 0.4.3\nWebP 1.2.2\nA Photoshop plug-in for reading and writing WebP files.\nCopyright 2019-2022 Google LLC.",5,10,10,180,50 106 | PUSHBUTTON "developers.google.com/speed/webp",10,10,60,180,20 107 | END 108 | 109 | #endif // English (United States) resources 110 | ///////////////////////////////////////////////////////////////////////////// 111 | 112 | 113 | 114 | #ifndef APSTUDIO_INVOKED 115 | ///////////////////////////////////////////////////////////////////////////// 116 | // 117 | // Generated from the TEXTINCLUDE 3 resource. 118 | // 119 | #include "WebPShop.pipl" 120 | 121 | ///////////////////////////////////////////////////////////////////////////// 122 | #endif // not APSTUDIO_INVOKED 123 | 124 | -------------------------------------------------------------------------------- /common/WebPShopSelectorReadLayer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "PIFormat.h" 16 | #include "WebPShop.h" 17 | #include "WebPShopSelector.h" 18 | #include "webp/demux.h" 19 | 20 | void InitAnimDecoder(FormatRecordPtr format_record, Data* const data, 21 | int16* const result) { 22 | WebPData webp_data; 23 | webp_data.bytes = (uint8_t*)data->file_data; 24 | webp_data.size = data->file_size; 25 | 26 | data->anim_decoder = WebPAnimDecoderNew(&webp_data, nullptr); 27 | if (data->anim_decoder == nullptr) { 28 | LOG("/!\\ WebPAnimDecoderNew() failed."); 29 | *result = readErr; 30 | return; 31 | } 32 | 33 | WebPAnimInfo info; 34 | if (!WebPAnimDecoderGetInfo(data->anim_decoder, &info)) { 35 | LOG("/!\\ WebPAnimDecoderGetInfo() failed."); 36 | *result = readErr; 37 | } else if (info.frame_count == 0) { 38 | LOG("/!\\ There is no frame to decode."); 39 | *result = readErr; 40 | } else { 41 | format_record->layerData = info.frame_count; 42 | data->last_frame_timestamp = 0; 43 | LOG("Will decode " << format_record->layerData << " frames."); 44 | } 45 | 46 | if (*result != noErr) ReleaseAnimDecoder(format_record, data); 47 | } 48 | 49 | void ReadOneFrame(FormatRecordPtr format_record, Data* const data, 50 | int16* const result, int frame_counter) { 51 | START_TIMER(ReadOneFrame); 52 | if (WebPAnimDecoderHasMoreFrames(data->anim_decoder)) { 53 | uint8_t* buf; 54 | int timestamp; 55 | if (!WebPAnimDecoderGetNext(data->anim_decoder, &buf, ×tamp)) { 56 | LOG("/!\\ WebPAnimDecoderGetNext() failed"); 57 | *result = readErr; 58 | return; 59 | } 60 | format_record->theRect.top = 0; 61 | format_record->theRect.bottom = format_record->imageSize.v; 62 | format_record->theRect.left = 0; 63 | format_record->theRect.right = format_record->imageSize.h; 64 | format_record->theRect32.top = 0; 65 | format_record->theRect32.bottom = format_record->imageSize32.v; 66 | format_record->theRect32.left = 0; 67 | format_record->theRect32.right = format_record->imageSize32.h; 68 | // Leave blendMode and opacity as is, it works. 69 | 70 | SetPlaneColRowBytes(format_record); 71 | 72 | format_record->data = buf; 73 | 74 | *result = format_record->advanceState(); 75 | format_record->progressProc(1, 1); 76 | format_record->data = nullptr; 77 | 78 | // Only rename the layer if it is an animated WebP. 79 | if (data->read_config.input.has_animation) { 80 | const int frame_duration = timestamp - data->last_frame_timestamp; 81 | const size_t layer_name_buffer_length = 82 | sizeof(data->layer_name_buffer) / sizeof(data->layer_name_buffer[0]); 83 | PrintDuration(frame_counter, frame_duration, data->layer_name_buffer, 84 | layer_name_buffer_length); 85 | format_record->layerName = data->layer_name_buffer; 86 | data->last_frame_timestamp = timestamp; 87 | } 88 | } else { 89 | format_record->data = nullptr; 90 | } 91 | STOP_TIMER(ReadOneFrame); 92 | } 93 | 94 | void ReleaseAnimDecoder(FormatRecordPtr format_record, Data* const data) { 95 | WebPAnimDecoderDelete(data->anim_decoder); 96 | data->anim_decoder = nullptr; 97 | format_record->data = nullptr; 98 | Deallocate(&data->file_data); 99 | } 100 | 101 | //------------------------------------------------------------------------------ 102 | 103 | void DoReadLayerStart(FormatRecordPtr format_record, Data* const data, 104 | int16* const result) { 105 | if (*result != noErr) ReleaseAnimDecoder(format_record, data); 106 | } 107 | 108 | //------------------------------------------------------------------------------ 109 | 110 | void DoReadLayerContinue(FormatRecordPtr format_record, Data* const data, 111 | int16* const result) { 112 | ReadOneFrame(format_record, data, result, format_record->layerData); 113 | 114 | if (*result != noErr) ReleaseAnimDecoder(format_record, data); 115 | } 116 | 117 | //------------------------------------------------------------------------------ 118 | 119 | void DoReadLayerFinish(FormatRecordPtr format_record, Data* const data, 120 | int16* const result) { 121 | if (*result != noErr) ReleaseAnimDecoder(format_record, data); 122 | } 123 | -------------------------------------------------------------------------------- /win/WebPShop.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {bc6a55fa-db96-4c59-8de0-0f64f592ad47} 6 | cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90 7 | 8 | 9 | {d857c2fe-99e0-48b0-a3c2-3ef92aa3d551} 10 | h;hpp;hxx;hm;inl;fi;fd 11 | 12 | 13 | {c468bb1c-7590-448d-95c3-786bdee44b30} 14 | ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe 15 | 16 | 17 | {7b018abd-e20d-49aa-93dc-b5335be21d08} 18 | *.c;*.cpp; 19 | 20 | 21 | 22 | 23 | Source Files 24 | 25 | 26 | Source Files 27 | 28 | 29 | Source Files 30 | 31 | 32 | Common Files 33 | 34 | 35 | Common Files 36 | 37 | 38 | Common Files 39 | 40 | 41 | Common Files 42 | 43 | 44 | Common Files 45 | 46 | 47 | Common Files 48 | 49 | 50 | Common Files 51 | 52 | 53 | Common Files 54 | 55 | 56 | Source Files 57 | 58 | 59 | Source Files 60 | 61 | 62 | Source Files 63 | 64 | 65 | Source Files 66 | 67 | 68 | Source Files 69 | 70 | 71 | Source Files 72 | 73 | 74 | Source Files 75 | 76 | 77 | Source Files 78 | 79 | 80 | Source Files 81 | 82 | 83 | Source Files 84 | 85 | 86 | Source Files 87 | 88 | 89 | Source Files 90 | 91 | 92 | Source Files 93 | 94 | 95 | Source Files 96 | 97 | 98 | Source Files 99 | 100 | 101 | Source Files 102 | 103 | 104 | Source Files 105 | 106 | 107 | Source Files 108 | 109 | 110 | 111 | 112 | Header Files 113 | 114 | 115 | Header Files 116 | 117 | 118 | Header Files 119 | 120 | 121 | Header Files 122 | 123 | 124 | Header Files 125 | 126 | 127 | 128 | 129 | Resource Files 130 | 131 | 132 | 133 | 134 | Resource Files 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /common/WebPShopEncodeAnimUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "WebPShop.h" 16 | #include "webp/mux.h" 17 | 18 | bool TryExtractDuration(const uint16* const layer_name, 19 | int* const duration_ms) { 20 | if (layer_name == nullptr || duration_ms == nullptr) return false; 21 | *duration_ms = -1; 22 | 23 | const int max_num_browsed_characters = 256; // Safety net. 24 | const int max_milliseconds = (1 << 30) / 10; // No overflow. 25 | enum WaitingFor { openingParenthesis, number, ms, closingParenthesis }; 26 | 27 | WaitingFor waiting_for = WaitingFor::openingParenthesis; 28 | int milliseconds = -1; 29 | for (int i = 0; layer_name[i] != 0 && i < max_num_browsed_characters; ++i) { 30 | if (layer_name[i] == ' ') continue; // Ignore spaces. 31 | 32 | if (waiting_for == WaitingFor::number) { // Parse positive integer. 33 | if (layer_name[i] >= '0' && layer_name[i] <= '9' && 34 | milliseconds <= max_milliseconds) { 35 | if (milliseconds == -1) milliseconds = 0; // First digit. 36 | milliseconds = milliseconds * 10 + (layer_name[i] - '0'); 37 | } else if (milliseconds > -1) { // We found a number. 38 | waiting_for = WaitingFor::ms; // Carry on. 39 | } else { // We didn't find any digit. 40 | waiting_for = WaitingFor::openingParenthesis; // Restart. 41 | } 42 | } 43 | 44 | if (waiting_for == WaitingFor::ms) { // Parse case-insensitive 'ms'. 45 | if (layer_name[i] == 'M' || layer_name[i] == 'm') { 46 | ++i; 47 | if (layer_name[i] == 'S' || layer_name[i] == 's') { 48 | waiting_for = WaitingFor::closingParenthesis; // Carry on. 49 | } else { 50 | waiting_for = WaitingFor::openingParenthesis; // Restart. 51 | } 52 | } else { 53 | waiting_for = WaitingFor::openingParenthesis; // Restart. 54 | } 55 | } else if (waiting_for == WaitingFor::closingParenthesis) { 56 | if (layer_name[i] == ')') { 57 | *duration_ms = milliseconds; // Success. 58 | } 59 | waiting_for = WaitingFor::openingParenthesis; // Restart. 60 | } 61 | 62 | if (waiting_for == WaitingFor::openingParenthesis) { 63 | if (layer_name[i] == '(') { 64 | milliseconds = -1; // No digit found yet. 65 | waiting_for = WaitingFor::number; // Carry on. 66 | } 67 | } 68 | } 69 | return *duration_ms > -1; 70 | } 71 | 72 | //------------------------------------------------------------------------------ 73 | 74 | bool EncodeAllFrames(const std::vector& original_frames, 75 | const WriteConfig& write_config, 76 | WebPData* const encoded_data) { 77 | START_TIMER(EncodeAllFrames); 78 | 79 | if (original_frames.empty() || encoded_data == nullptr) { 80 | LOG("/!\\ Bad input/output."); 81 | return false; 82 | } 83 | 84 | WebPConfig config; 85 | if (!WebPConfigInit(&config)) { 86 | LOG("/!\\ WebPConfigInit() failed."); 87 | return false; 88 | } 89 | SetWebPConfig(&config, write_config); 90 | 91 | WebPPicture pic; 92 | if (!WebPPictureInit(&pic)) { 93 | LOG("/!\\ WebPPictureInit() failed."); 94 | return false; 95 | } 96 | 97 | WebPAnimEncoderOptions anim_encoder_options; 98 | if (!WebPAnimEncoderOptionsInit(&anim_encoder_options)) { 99 | LOG("/!\\ WebPAnimEncoderOptionsInit() failed."); 100 | return false; 101 | } 102 | anim_encoder_options.anim_params.loop_count = 103 | write_config.loop_forever ? 0 : 1; 104 | 105 | WebPAnimEncoder* anim_encoder = WebPAnimEncoderNew( 106 | original_frames[0].image.width, original_frames[0].image.height, 107 | &anim_encoder_options); 108 | if (anim_encoder == nullptr) { 109 | LOG("/!\\ WebPAnimEncoderNew() failed."); 110 | return false; 111 | } 112 | 113 | int timestamp_ms = 0; 114 | 115 | for (size_t i = 0; i < original_frames.size(); ++i) { 116 | const FrameMemoryDesc& frame = original_frames[i]; 117 | if (!CastToWebPPicture(config, frame.image, &pic)) { 118 | WebPPictureFree(&pic); 119 | WebPAnimEncoderDelete(anim_encoder); 120 | return false; 121 | } 122 | 123 | if (!WebPAnimEncoderAdd(anim_encoder, &pic, timestamp_ms, &config)) { 124 | LOG("/!\\ WebPAnimEncoderAdd failed (" << pic.error_code << ")."); 125 | WebPPictureFree(&pic); 126 | WebPAnimEncoderDelete(anim_encoder); 127 | return false; 128 | } 129 | 130 | timestamp_ms += frame.duration_ms; 131 | } 132 | 133 | WebPPictureFree(&pic); 134 | 135 | if (!WebPAnimEncoderAdd(anim_encoder, nullptr, timestamp_ms, nullptr)) { 136 | LOG("/!\\ Last WebPAnimEncoderAdd() failed."); 137 | WebPAnimEncoderDelete(anim_encoder); 138 | return false; 139 | } 140 | 141 | WebPDataClear(encoded_data); 142 | if (!WebPAnimEncoderAssemble(anim_encoder, encoded_data)) { 143 | LOG("/!\\ WebPAnimEncoderAssemble() failed."); 144 | WebPAnimEncoderDelete(anim_encoder); 145 | return false; 146 | } 147 | 148 | WebPAnimEncoderDelete(anim_encoder); 149 | LOG("Encoded " << original_frames.size() << " frames into " 150 | << encoded_data->size << " bytes."); 151 | 152 | STOP_TIMER(EncodeAllFrames); 153 | return true; 154 | } 155 | -------------------------------------------------------------------------------- /common/WebPShopSelector.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef __WebPShopSelector_H__ 16 | #define __WebPShopSelector_H__ 17 | 18 | #include "PIFormat.h" 19 | #include "WebPShop.h" 20 | 21 | //------------------------------------------------------------------------------ 22 | 23 | // When the Open menu command is selected, DoReadPrepare() and DoReadStart() 24 | // will be called once, followed by DoReadContinue() as long as the host did not 25 | // receive the entire image (by chunks defined by format_record->theRect32). 26 | // DoReadFinish() is then called once for cleanup. 27 | // data->file_data contains input data and format_record->data must be filled 28 | // with decoded pixels, then null when it's over. 29 | void DoReadPrepare(FormatRecordPtr format_record, Data* const data, 30 | int16* const result); 31 | void DoReadStart(FormatRecordPtr format_record, Data* const data, 32 | int16* const result); 33 | void DoReadContinue(FormatRecordPtr format_record, Data* const data, 34 | int16* const result); 35 | void DoReadFinish(FormatRecordPtr format_record, Data* const data, 36 | int16* const result); 37 | 38 | // If FormatLayerSupport contains doesSupportFormatLayers in WebPShop.r, 39 | // DoReadContinue() is no longer called if format_record->layerData > 0; 40 | // it is replaced by DoReadLayerStart(), then multiple DoReadLayerContinue(), 41 | // then DoReadLayerFinish(). These three functions are called in this order as 42 | // many times as specified in format_record->layerData by the plugin. 43 | void DoReadLayerStart(FormatRecordPtr format_record, Data* const data, 44 | int16* const result); 45 | void DoReadLayerContinue(FormatRecordPtr format_record, Data* const data, 46 | int16* const result); 47 | void DoReadLayerFinish(FormatRecordPtr format_record, Data* const data, 48 | int16* const result); 49 | 50 | // When the Save menu command is selected, DoOptions*() are called. 51 | // It is time to show the user a dialog with encoding settings. 52 | // If a preview was shown, the encoded_data is reused for the writing step. 53 | void DoOptionsPrepare(FormatRecordPtr format_record, Data* const data, 54 | int16* const result); 55 | void DoOptionsStart(FormatRecordPtr format_record, Data* const data, 56 | int16* const result, const SPPluginRef& plugin_ref); 57 | void DoOptionsContinue(FormatRecordPtr format_record, Data* const data, 58 | int16* const result); 59 | void DoOptionsFinish(FormatRecordPtr format_record, Data* const data, 60 | int16* const result); 61 | 62 | // Then an estimation of the output file size is requested by the host. 63 | // It must be set in format_record->minDataBytes and maxDataBytes. 64 | void DoEstimatePrepare(FormatRecordPtr format_record, Data* const data, 65 | int16* const result); 66 | void DoEstimateStart(FormatRecordPtr format_record, Data* const data, 67 | int16* const result); 68 | void DoEstimateContinue(FormatRecordPtr format_record, Data* const data, 69 | int16* const result); 70 | void DoEstimateFinish(FormatRecordPtr format_record, Data* const data, 71 | int16* const result); 72 | 73 | // Then we write the encoded data to the file opened by the host. 74 | // DoWriteContinue() is called until the whole canvas was requested to the 75 | // host (by chunks defined by format_record->theRect32). 76 | // Input data is in format_record->data, and it must be set to null when it's 77 | // over. 78 | // Here format_record->data is not used; data is directly copied through 79 | // CopyWholeCanvas() and CopyAllLayers(), as used for the UI preview. 80 | void DoWritePrepare(FormatRecordPtr format_record, Data* const data, 81 | int16* const result); 82 | void DoWriteStart(FormatRecordPtr format_record, Data* const data, 83 | int16* const result); 84 | void DoWriteContinue(FormatRecordPtr format_record, Data* const data, 85 | int16* const result); 86 | void DoWriteFinish(FormatRecordPtr format_record, Data* const data, 87 | int16* const result); 88 | 89 | // If FormatLayerSupport is in WebPShop.r but not FormatLayerSupportReadOnly, 90 | // DoWriteContinue() is no longer called; it is replaced by DoWriteLayerStart(), 91 | // then multiple DoWriteLayerContinue(), then DoWriteLayerFinish(). These three 92 | // functions are called in this order as many times as specified in 93 | // format_record->layerData by the host. 94 | void DoWriteLayerStart(FormatRecordPtr format_record, Data* const data, 95 | int16* const result); 96 | void DoWriteLayerContinue(FormatRecordPtr format_record, Data* const data, 97 | int16* const result); 98 | void DoWriteLayerFinish(FormatRecordPtr format_record, Data* const data, 99 | int16* const result); 100 | 101 | // It's called by the host to know which plugins can open the file, 102 | // only if several installed plugins handle .webp files. 103 | void DoFilterFile(FormatRecordPtr format_record, Data* const data, 104 | int16* const result); 105 | 106 | //------------------------------------------------------------------------------ 107 | 108 | #endif // __WebPShopSelector_H__ 109 | -------------------------------------------------------------------------------- /common/WebPShopSelectorRead.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "PIFormat.h" 16 | #include "WebPShop.h" 17 | #include "WebPShopSelector.h" 18 | #include "webp/decode.h" 19 | 20 | void SetPlaneColRowBytes(FormatRecordPtr format_record) { 21 | format_record->loPlane = 0; 22 | format_record->hiPlane = format_record->planes - 1; 23 | 24 | format_record->planeBytes = format_record->depth / 8; 25 | format_record->colBytes = format_record->planeBytes * format_record->planes; 26 | format_record->rowBytes = 27 | format_record->colBytes * format_record->imageSize32.h; 28 | } 29 | 30 | //------------------------------------------------------------------------------ 31 | 32 | void DoReadPrepare(FormatRecordPtr format_record, Data* const data, 33 | int16* const result) { 34 | format_record->maxData = 0; // The maximum number of bytes Photoshop can free 35 | // up for a plug-in to use. 36 | LoadPOSIXConfig(format_record, result); 37 | } 38 | 39 | //------------------------------------------------------------------------------ 40 | 41 | void DoReadStart(FormatRecordPtr format_record, Data* const data, 42 | int16* const result) { 43 | ReadAndCheckHeader(format_record, result, &data->file_size); 44 | if (*result != noErr) return; 45 | 46 | LOG("Allocate and read " << data->file_size << " bytes."); 47 | AllocateAndRead(data->file_size, &data->file_data, format_record, result); 48 | if (*result != noErr) return; 49 | 50 | if (*result == noErr && !WebPInitDecoderConfig(&data->read_config)) { 51 | LOG("/!\\ WebPInitDecoderConfig() failed."); 52 | *result = readErr; 53 | } 54 | 55 | if (*result == noErr && 56 | WebPGetFeatures((uint8_t*)data->file_data, data->file_size, 57 | &data->read_config.input) != VP8_STATUS_OK) { 58 | LOG("/!\\ WebPGetFeatures() failed."); 59 | *result = readErr; 60 | } 61 | 62 | if (*result == noErr) { 63 | const WebPData encoded_data = {(uint8_t*)data->file_data, data->file_size}; 64 | if (!DecodeMetadata(encoded_data, data->metadata)) *result = readErr; 65 | } 66 | 67 | if (*result == noErr) { 68 | *result = SetHostMetadata(format_record, data->metadata); 69 | } 70 | DeallocateMetadata(data->metadata); 71 | 72 | if (*result == noErr) { 73 | format_record->PluginUsing32BitCoordinates = 74 | format_record->HostSupports32BitCoordinates; 75 | format_record->imageMode = plugInModeRGBColor; 76 | if (!(format_record->hostModes & (1 << format_record->imageMode))) { 77 | LOG("/!\\ Unsupported plugInModeRGBColor"); // Unlikely. 78 | } 79 | format_record->imageSize.h = data->read_config.input.width; 80 | format_record->imageSize.v = data->read_config.input.height; 81 | format_record->imageSize32.h = data->read_config.input.width; 82 | format_record->imageSize32.v = data->read_config.input.height; 83 | format_record->depth = sizeof(uint8_t) * 8; 84 | // WebPAnimDecoderNew() only outputs RGBA or BGRA. 85 | format_record->planes = 4; 86 | 87 | format_record->imageHRes = 72; 88 | format_record->imageVRes = 72; 89 | 90 | format_record->planeMap[0] = 0; // RGBA 91 | format_record->planeMap[1] = 1; 92 | format_record->planeMap[2] = 2; 93 | format_record->planeMap[3] = 3; 94 | 95 | SetPlaneColRowBytes(format_record); 96 | 97 | LOG("Host"); 98 | LOG(" transparencyPlane: " << format_record->transparencyPlane); 99 | 100 | format_record->transparencyPlane = 3; 101 | format_record->transparencyMatting = 0; 102 | 103 | LOG("Params (" << data->read_config.input.width << "x" 104 | << data->read_config.input.height << "px)"); 105 | LOG(" imageMode: " << format_record->imageMode); 106 | LOG(" depth: " << format_record->depth); 107 | LOG(" planes: " << format_record->planes); 108 | LOG(" layerData: " << format_record->layerData); 109 | 110 | LOG(" loPlane: " << format_record->loPlane); 111 | LOG(" hiPlane: " << format_record->hiPlane); 112 | 113 | LOG(" planeBytes: " << format_record->planeBytes); 114 | LOG(" colBytes: " << format_record->colBytes); 115 | LOG(" rowBytes: " << format_record->rowBytes); 116 | 117 | LOG(" transparencyPlane: " << format_record->transparencyPlane); 118 | LOG(" transparencyMatting: " << format_record->transparencyMatting); 119 | LOG(" transparentIndex: " << format_record->transparentIndex); 120 | 121 | LOG(" hostInSecondThread: " << (int)format_record->hostInSecondaryThread); 122 | LOG(" openAsSmartObject: " << (int)format_record->openAsSmartObject); 123 | } 124 | 125 | // Going through formatSelectorReadContinue discards the alpha samples for 126 | // some reason. Treat all images, including still ones, as animations to use 127 | // formatSelectorReadLayerContinue which supports transparency. 128 | if (*result == noErr) InitAnimDecoder(format_record, data, result); 129 | // format_record->layerData = 0; // Uncomment for formatSelectorReadContinue 130 | 131 | if (*result != noErr) Deallocate(&data->file_data); 132 | } 133 | 134 | //------------------------------------------------------------------------------ 135 | 136 | // FormatLayerSupport in WebPShop.r redirects to DoReadLayerStart() for 137 | // animations. DoReadContinue() is called only for Smart Objects. 138 | void DoReadContinue(FormatRecordPtr format_record, Data* const data, 139 | int16* const result) { 140 | ReadOneFrame(format_record, data, result, /*frame_counter=*/0); 141 | if (*result != noErr) ReleaseAnimDecoder(format_record, data); 142 | 143 | Deallocate(&data->file_data); 144 | } 145 | 146 | //------------------------------------------------------------------------------ 147 | 148 | void DoReadFinish(FormatRecordPtr format_record, Data* const data, 149 | int16* const result) { 150 | ReleaseAnimDecoder(format_record, data); 151 | 152 | Deallocate(&format_record->data); 153 | Deallocate(&data->file_data); 154 | 155 | AddComment(format_record, data, result); // Write a history comment. 156 | } 157 | -------------------------------------------------------------------------------- /common/WebPShopScripting.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "PIActions.h" 16 | #include "PIUSuites.h" 17 | #include "WebPShop.h" 18 | 19 | static void LoadScriptingParameters(FormatRecordPtr format_record, 20 | WriteConfig* const write_config, 21 | bool* const use_posix, 22 | int16* const result) { 23 | DescriptorKeyID key = 0; 24 | DescriptorTypeID type = 0; 25 | DescriptorKeyIDArray array = {NULLID}; 26 | int32 flags = 0; 27 | 28 | PIDescriptorParameters* descParams = format_record->descriptorParameters; 29 | if (descParams == nullptr) { 30 | LOG("/!\\ Reading parameters: No descriptorParameters."); 31 | return; 32 | } 33 | 34 | ReadDescriptorProcs* readProcs = 35 | format_record->descriptorParameters->readDescriptorProcs; 36 | if (readProcs == nullptr) { 37 | LOG("/!\\ Reading parameters: No readDescriptorProcs."); 38 | return; 39 | } 40 | if (descParams->descriptor == nullptr) { 41 | LOG("Reading parameters: No descriptor (no stored parameter)."); 42 | return; 43 | } 44 | 45 | PIReadDescriptor token = 46 | readProcs->openReadDescriptorProc(descParams->descriptor, array); 47 | if (token == nullptr) { 48 | LOG("/!\\ Reading parameters: No token."); 49 | return; 50 | } 51 | 52 | while (readProcs->getKeyProc(token, &key, &type, &flags)) { 53 | switch (key) { 54 | case keyWriteConfig_quality: { 55 | int32 i; 56 | readProcs->getIntegerProc(token, &i); 57 | if (i < 0 || i > 100) { 58 | LOG("/!\\ Reading parameters: Out of bounds."); 59 | } else if (write_config != nullptr) { 60 | write_config->quality = i; 61 | } 62 | LOG("Reading parameter: quality = " << i); 63 | break; 64 | } 65 | case keyWriteConfig_compression: { 66 | int32 i; 67 | readProcs->getIntegerProc(token, &i); 68 | if (i < (int32)Compression::FASTEST || 69 | i > (int32)Compression::SLOWEST) { 70 | LOG("/!\\ Reading parameters: Out of bounds."); 71 | } else if (write_config != nullptr) { 72 | write_config->compression = (Compression)i; 73 | } 74 | LOG("Reading parameter: compression = " << i); 75 | break; 76 | } 77 | case keyWriteConfig_keep_exif: { 78 | Boolean b; 79 | readProcs->getBooleanProc(token, &b); 80 | if (write_config != nullptr) write_config->keep_exif = (bool)b; 81 | LOG("Reading parameter: exif = " << (bool)b); 82 | break; 83 | } 84 | case keyWriteConfig_keep_xmp: { 85 | Boolean b; 86 | readProcs->getBooleanProc(token, &b); 87 | if (write_config != nullptr) write_config->keep_xmp = (bool)b; 88 | LOG("Reading parameter: xmp = " << (bool)b); 89 | break; 90 | } 91 | case keyWriteConfig_keep_color_profile: { 92 | Boolean b; 93 | readProcs->getBooleanProc(token, &b); 94 | if (write_config != nullptr) write_config->keep_color_profile = (bool)b; 95 | LOG("Reading parameter: color profile = " << (bool)b); 96 | break; 97 | } 98 | case keyWriteConfig_loop_forever: { 99 | Boolean b; 100 | readProcs->getBooleanProc(token, &b); 101 | if (write_config != nullptr) write_config->loop_forever = (bool)b; 102 | LOG("Reading parameter: loop forever = " << (bool)b); 103 | break; 104 | } 105 | case keyUsePOSIX: { 106 | Boolean b; 107 | readProcs->getBooleanProc(token, &b); 108 | if (use_posix != nullptr) *use_posix = (bool)b; 109 | LOG("Reading parameter: posix = " << (bool)b); 110 | break; 111 | } 112 | default: { 113 | LOG("Ignoring parameter key: " << key); 114 | break; 115 | } 116 | } 117 | } 118 | 119 | *result = readProcs->closeReadDescriptorProc(token); 120 | sPSHandle->Dispose(descParams->descriptor); 121 | descParams->descriptor = nullptr; 122 | 123 | if (*result != noErr) { 124 | LOG("/!\\ Reading parameters: Error " << *result); 125 | } 126 | } 127 | 128 | void LoadWriteConfig(FormatRecordPtr format_record, 129 | WriteConfig* const write_config, int16* const result) { 130 | LoadScriptingParameters(format_record, write_config, nullptr, result); 131 | } 132 | 133 | void SaveWriteConfig(FormatRecordPtr format_record, 134 | const WriteConfig& write_config, int16* const result) { 135 | PIDescriptorParameters* descParams = format_record->descriptorParameters; 136 | if (descParams == NULL) { 137 | LOG("/!\\ Writing parameters: No descriptorParameters."); 138 | return; 139 | } 140 | 141 | WriteDescriptorProcs* writeProcs = descParams->writeDescriptorProcs; 142 | if (writeProcs == NULL) { 143 | LOG("/!\\ Writing parameters: No writeDescriptorProcs."); 144 | return; 145 | } 146 | 147 | PIWriteDescriptor token = writeProcs->openWriteDescriptorProc(); 148 | if (token == NULL) { 149 | LOG("/!\\ Writing parameters: No token."); 150 | return; 151 | } 152 | 153 | LOG("Writing parameters: quality = " << write_config.quality); 154 | LOG(" compression = " << write_config.compression); 155 | LOG(" keep " 156 | << (write_config.keep_exif ? "EXIF" : "NO EXIF") << ", " 157 | << (write_config.keep_xmp ? "XMP" : "NO XMP") << ", " 158 | << (write_config.keep_color_profile ? "ICC" : "NO ICC")); 159 | LOG(" loop forever = " 160 | << (write_config.loop_forever ? "yes" : "no")); 161 | 162 | writeProcs->putIntegerProc(token, keyWriteConfig_quality, 163 | write_config.quality); 164 | writeProcs->putIntegerProc(token, keyWriteConfig_compression, 165 | write_config.compression); 166 | writeProcs->putBooleanProc(token, keyWriteConfig_keep_exif, 167 | write_config.keep_exif); 168 | writeProcs->putBooleanProc(token, keyWriteConfig_keep_xmp, 169 | write_config.keep_xmp); 170 | writeProcs->putBooleanProc(token, keyWriteConfig_keep_color_profile, 171 | write_config.keep_color_profile); 172 | writeProcs->putBooleanProc(token, keyWriteConfig_loop_forever, 173 | write_config.loop_forever); 174 | 175 | sPSHandle->Dispose(descParams->descriptor); 176 | PIDescriptorHandle h; 177 | *result = writeProcs->closeWriteDescriptorProc(token, &h); 178 | descParams->descriptor = h; 179 | 180 | if (*result != noErr) { 181 | LOG("/!\\ Writing parameters: Error " << *result); 182 | } 183 | } 184 | 185 | void LoadPOSIXConfig(FormatRecordPtr format_record, int16* const result) { 186 | bool use_posix; 187 | LoadScriptingParameters(format_record, nullptr, &use_posix, result); 188 | format_record->pluginUsingPOSIXIO = format_record->hostSupportsPOSIXIO; 189 | } 190 | -------------------------------------------------------------------------------- /common/WebPShopDecodeUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "PIProperties.h" 16 | #include "WebPShop.h" 17 | #include "webp/decode.h" 18 | #include "webp/demux.h" 19 | 20 | bool DecodeOneImage(const WebPData& encoded_data, 21 | ImageMemoryDesc* const compressed_image) { 22 | START_TIMER(DecodeOneImage); 23 | 24 | if (encoded_data.bytes == nullptr || compressed_image == nullptr) { 25 | LOG("/!\\ Source or destination is null."); 26 | return false; 27 | } 28 | 29 | WebPDecoderConfig decoder_config; 30 | VP8StatusCode status; 31 | if (!WebPInitDecoderConfig(&decoder_config)) { 32 | LOG("/!\\ WebPInitDecoderConfig failed."); 33 | return false; 34 | } 35 | if ((status = WebPGetFeatures(encoded_data.bytes, encoded_data.size, 36 | &decoder_config.input)) != VP8_STATUS_OK) { 37 | LOG("/!\\ WebPGetFeatures failed (" << status << ")"); 38 | return false; 39 | } 40 | if (!AllocateImage(compressed_image, decoder_config.input.width, 41 | decoder_config.input.height, /*num_channels=*/4, 42 | /*bit_depth=*/8)) { 43 | LOG("/!\\ AllocateImage failed."); 44 | return false; 45 | } 46 | 47 | // Will always be RGBA since we force 4 channels. 48 | decoder_config.output.colorspace = MODE_RGBA; 49 | decoder_config.output.u.RGBA.rgba = (uint8_t*)compressed_image->pixels.data; 50 | decoder_config.output.u.RGBA.stride = 51 | (int)(compressed_image->pixels.rowBits / 8); 52 | decoder_config.output.u.RGBA.size = (size_t)( 53 | decoder_config.output.u.RGBA.stride * decoder_config.input.height); 54 | decoder_config.output.is_external_memory = 1; 55 | decoder_config.output.width = decoder_config.input.width; 56 | decoder_config.output.height = decoder_config.input.height; 57 | 58 | if ((status = WebPDecode(encoded_data.bytes, encoded_data.size, 59 | &decoder_config)) != VP8_STATUS_OK) { 60 | LOG("/!\\ WebPDecode failed (" << status << ")"); 61 | WebPFreeDecBuffer(&decoder_config.output); 62 | return false; 63 | } 64 | WebPFreeDecBuffer(&decoder_config.output); 65 | LOG("Decoded " << encoded_data.size << " bytes."); 66 | 67 | STOP_TIMER(DecodeOneImage); 68 | return true; 69 | } 70 | 71 | bool DecodeMetadata(const WebPData& encoded_data, 72 | Metadata metadata[Metadata::kNum]) { 73 | DeallocateMetadata(metadata); // Get rid of any previous data. 74 | 75 | WebPDemuxer* const demux = WebPDemux(&encoded_data); 76 | if (demux == nullptr) { 77 | LOG("/!\\ WebPDemux failed."); 78 | return false; 79 | } 80 | 81 | bool success = true; 82 | for (int i = 0; success && i < Metadata::kNum; ++i) { 83 | WebPChunkIterator iter; 84 | const int found = WebPDemuxGetChunk(demux, metadata[i].four_cc, 0, &iter); 85 | if (found != 0 && iter.chunk.bytes != nullptr && iter.chunk.size > 0) { 86 | if (!WebPDataCopy(&iter.chunk, &metadata[i].chunk)) { 87 | LOG("/!\\ WebPDataCopy of " << metadata[i].four_cc << " chunk (" 88 | << iter.chunk.size << " bytes) failed."); 89 | success = false; 90 | } else { 91 | LOG("Retrieved " << metadata[i].four_cc << " chunk (" 92 | << metadata[i].chunk.size << " bytes)."); 93 | } 94 | } 95 | // Only the last chunk of each type is imported. 96 | WebPDemuxReleaseChunkIterator(&iter); 97 | } 98 | WebPDemuxDelete(demux); 99 | return success; 100 | } 101 | 102 | static OSErr SetHostProperty(const Metadata& metadata, PIType key) { 103 | OSErr result = noErr; 104 | const WebPData& chunk = metadata.chunk; 105 | if (chunk.bytes != nullptr && chunk.size > 0) { 106 | // Code below is inspired from PISetXMP() and PISetEXIFData(). 107 | if (sPSHandle->New == nullptr) { 108 | LOG("/!\\ sPSHandle->New is null"); 109 | result = errPlugInHostInsufficient; 110 | } else { 111 | Handle handle = sPSHandle->New((int32)chunk.size); 112 | if (handle == nullptr) { 113 | LOG("/!\\ Could not allocate " << chunk.size 114 | << " bytes with sPSHandle->New()"); 115 | result = memFullErr; 116 | } else { 117 | Boolean oldLock = FALSE; 118 | Ptr ptr = nullptr; 119 | sPSHandle->SetLock(handle, true, &ptr, &oldLock); 120 | if (ptr == nullptr) { 121 | LOG("/!\\ SetLock failed"); 122 | result = vLckdErr; 123 | } else { 124 | std::copy(chunk.bytes, chunk.bytes + chunk.size, ptr); 125 | sPSHandle->SetLock(handle, false, &ptr, &oldLock); 126 | result = sPSProperty->setPropertyProc(kPhotoshopSignature, key, 0, 127 | NULL, handle); 128 | if (result != noErr) { 129 | LOG("/!\\ setPropertyProc failed (" << result << ")"); 130 | } else { 131 | LOG("Set " << chunk.size << " bytes of " << metadata.four_cc); 132 | } 133 | } 134 | if (result != noErr) sPSHandle->Dispose(handle); 135 | } 136 | } 137 | } 138 | return result; 139 | } 140 | 141 | static OSErr SetHostICCProfile(FormatRecordPtr format_record, 142 | const Metadata& metadata) { 143 | OSErr result = noErr; 144 | const WebPData& chunk = metadata.chunk; 145 | if (chunk.bytes != nullptr && chunk.size > 0) { 146 | if (format_record->handleProcs == nullptr) { 147 | LOG("handleProcs is null, considering no ICCP"); 148 | } else if (format_record->handleProcs->lockProc == nullptr) { 149 | LOG("handleProcs->lockProc is null, considering no ICCP"); 150 | } else if (format_record->handleProcs->unlockProc == nullptr) { 151 | LOG("handleProcs->lockProc is null, considering no ICCP"); 152 | } else { 153 | const int32 size = (int32)chunk.size; 154 | Handle handle = sPSHandle->New(size); 155 | if (handle == nullptr) { 156 | LOG("/!\\ Could not allocate " << size 157 | << " bytes with sPSHandle->New()"); 158 | result = memFullErr; 159 | } else { 160 | Ptr ptr = format_record->handleProcs->lockProc(handle, false); 161 | if (ptr == nullptr) { 162 | LOG("/!\\ lockProc failed"); 163 | result = vLckdErr; 164 | sPSHandle->Dispose(handle); 165 | } else { 166 | std::copy(chunk.bytes, chunk.bytes + chunk.size, ptr); 167 | format_record->handleProcs->unlockProc(handle); 168 | 169 | format_record->iCCprofileData = handle; 170 | format_record->iCCprofileSize = size; 171 | LOG("Set " << size << " bytes of color profile."); 172 | } 173 | } 174 | } 175 | } 176 | return result; 177 | } 178 | 179 | OSErr SetHostMetadata(FormatRecordPtr format_record, 180 | const Metadata metadata[Metadata::kNum]) { 181 | OSErr result = SetHostProperty(metadata[Metadata::kEXIF], propEXIFData); 182 | if (result != noErr) return result; 183 | 184 | result = SetHostProperty(metadata[Metadata::kXMP], propXMP); 185 | if (result != noErr) return result; 186 | 187 | result = SetHostICCProfile(format_record, metadata[Metadata::kICCP]); 188 | if (result != noErr) return result; 189 | 190 | return noErr; 191 | } 192 | -------------------------------------------------------------------------------- /common/WebPShopUI.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef __WebPShopUI_H__ 16 | #define __WebPShopUI_H__ 17 | 18 | #include "PIUI.h" 19 | #include "WebPShop.h" 20 | 21 | //------------------------------------------------------------------------------ 22 | // UI element id (also defined in win/WebPShop.rc) 23 | 24 | const int16 kDNone = -1; 25 | 26 | const int16 kDOK = 1; 27 | const int16 kDCancel = 2; 28 | const int16 kDWebPText = 5; 29 | const int16 kDQualitySlider = 11; 30 | const int16 kDQualityField = 12; 31 | const int16 kDCompressionFastest = 21; 32 | const int16 kDCompressionDefault = 22; 33 | const int16 kDCompressionSlowest = 23; 34 | const int16 kDKeepExif = 33; 35 | const int16 kDKeepXmp = 34; 36 | const int16 kDKeepColorProfile = 35; 37 | const int16 kDLoopForever = 38; 38 | const int16 kDProxy = 41; 39 | const int16 kDProxyCheckbox = 42; 40 | const int16 kDFrameText = 45; 41 | const int16 kDFrameSlider = 46; 42 | const int16 kDFrameField = 47; 43 | const int16 kDFrameDurationText = 48; 44 | 45 | const int16 kDAboutWebPLink = 10; 46 | 47 | //------------------------------------------------------------------------------ 48 | // Platform-dependent painting context 49 | 50 | struct PaintingContext { 51 | #ifdef __PIMac__ 52 | void* cg_context; // CGContextRef instance 53 | void* proxy_view; // WebPShopProxyView instance 54 | #else 55 | PAINTSTRUCT ps; 56 | HDC hDC; 57 | #endif 58 | }; 59 | 60 | //------------------------------------------------------------------------------ 61 | // Utils 62 | 63 | VRect NullRect(); 64 | VRect GetCenteredRectInArea(const VRect& area, int32 width, int32 height); 65 | VRect GetCropAreaRectInWindow(const VRect& proxy_area_rect_in_window); 66 | VRect GetScaleAreaRectInWindow(const VRect& proxy_area_rect_in_window); 67 | std::string DataSizeToString(size_t data_size); 68 | void SetErrorString(FormatRecordPtr format_record, const std::string& str); 69 | 70 | //------------------------------------------------------------------------------ 71 | // UI elements (inspired from Adobe SDK) 72 | 73 | class PISlider { 74 | PIItem item_; 75 | 76 | public: 77 | PISlider() : item_(NULL) {} 78 | ~PISlider() {} 79 | 80 | void SetItem(PIDialogPtr dialog, int16 item_id, int min, int max); 81 | void SetValue(int value); 82 | int GetValue(void); 83 | void SetValueIfDifferent(int value) { 84 | if (GetValue() != value) SetValue(value); 85 | } 86 | }; 87 | 88 | class PIIntegerField { 89 | PIDialogPtr dialog_; 90 | int16 item_id_; 91 | int min_, max_; 92 | 93 | public: 94 | PIIntegerField() : dialog_(nullptr), item_id_(-1), min_(0), max_(0) {} 95 | ~PIIntegerField() {} 96 | 97 | void SetItem(PIDialogPtr dialog, int16 item_id, int min, int max); 98 | void SetValue(int value); 99 | int GetValue(void); 100 | void SetValueIfDifferent(int value) { 101 | if (GetValue() != value) SetValue(value); 102 | } 103 | }; 104 | 105 | //------------------------------------------------------------------------------ 106 | // UI window and element instances 107 | 108 | class WebPShopDialog : public PIDialog { 109 | private: 110 | // UI elements 111 | PIText webp_text_; 112 | PISlider quality_slider_; 113 | PIIntegerField quality_field_; 114 | PIRadioGroup compression_radio_group_; 115 | PICheckBox keep_exif_checkbox_; 116 | PICheckBox keep_xmp_checkbox_; 117 | PICheckBox keep_color_profile_checkbox_; 118 | PICheckBox loop_forever_checkbox_; 119 | PICheckBox proxy_checkbox_; 120 | PISlider frame_slider_; 121 | PIIntegerField frame_field_; 122 | PIText frame_duration_text_; 123 | 124 | // Settings 125 | WriteConfig write_config_; 126 | const Metadata* metadata_; 127 | 128 | // Currently displayed 129 | size_t frame_index_; 130 | VRect selection_in_compressed_frame_; 131 | 132 | // Before encoding 133 | const std::vector& original_frames_; 134 | const bool original_frames_were_converted_to_8b_; 135 | // After encoding 136 | WebPData* const encoded_data_; 137 | // After decoding (for proxy) 138 | std::vector compressed_frames_; 139 | std::vector scaled_compressed_frames_; 140 | ImageMemoryDesc cropped_compressed_frame_; 141 | bool update_cropped_compressed_frame_; // If frame or selection changed. 142 | 143 | // Adobe SDK portable display function 144 | DisplayPixelsProc display_pixels_proc_; 145 | 146 | // Clear 147 | void DiscardEncodedData(void); 148 | void OnError(void); 149 | 150 | // Platform-dependent 151 | PIItem GetItem(short item); 152 | void ShowItem(short item); 153 | void HideItem(short item); 154 | void EnableItem(short item); 155 | void DisableItem(short item); 156 | VRect GetProxyAreaRectInWindow(void); 157 | void BeginPainting(PaintingContext* const painting_context); 158 | void EndPainting(PaintingContext* const painting_context); 159 | void ClearRect(const VRect& rect, PaintingContext* const painting_context); 160 | void DrawRectBorder(uint8_t r, uint8_t g, uint8_t b, 161 | int left, int top, int right, int bottom, 162 | PaintingContext* const painting_context); 163 | bool DisplayImage(const ImageMemoryDesc& image, const VRect& rect, 164 | DisplayPixelsProc display_pixels_proc, 165 | PaintingContext* const painting_context); 166 | void TriggerRepaint(); 167 | 168 | public: 169 | WebPShopDialog(const WriteConfig& write_config, 170 | const Metadata metadata[Metadata::kNum], 171 | const std::vector& original_frames, 172 | bool original_frames_were_converted_to_8b, 173 | WebPData* const encoded_data, 174 | DisplayPixelsProc display_pixels_proc) 175 | : PIDialog(), 176 | webp_text_(), 177 | quality_slider_(), 178 | quality_field_(), 179 | compression_radio_group_(), 180 | keep_exif_checkbox_(), 181 | keep_xmp_checkbox_(), 182 | keep_color_profile_checkbox_(), 183 | loop_forever_checkbox_(), 184 | proxy_checkbox_(), 185 | frame_slider_(), 186 | frame_field_(), 187 | frame_duration_text_(), 188 | write_config_(write_config), 189 | metadata_(metadata), 190 | frame_index_(0), 191 | selection_in_compressed_frame_(), 192 | original_frames_(original_frames), 193 | original_frames_were_converted_to_8b_( 194 | original_frames_were_converted_to_8b), 195 | encoded_data_(encoded_data), 196 | compressed_frames_(), 197 | scaled_compressed_frames_(), 198 | cropped_compressed_frame_(), 199 | update_cropped_compressed_frame_(true), 200 | display_pixels_proc_(display_pixels_proc) {} 201 | ~WebPShopDialog() { DeallocateCompressedFrames(); } 202 | 203 | void DeallocateCompressedFrames(void); 204 | const WriteConfig& GetWriteConfig(void) const { return write_config_; } 205 | 206 | void Init(void) override; 207 | 208 | void ForceRepaint(void); 209 | void ClearProxyArea(void); 210 | void PaintProxy(void); 211 | 212 | void Notify(int32 item) override; 213 | void OnMouseMove(int x, int y, bool left_button_is_held_down); 214 | 215 | // Platform-dependent callback registration. 216 | int Modal(SPPluginRef plugin, const char* name, int ID) override; 217 | }; 218 | 219 | #endif // __WebPShopUI_H__ 220 | -------------------------------------------------------------------------------- /common/WebPShopImageUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "WebPShop.h" 16 | 17 | #include 18 | 19 | //------------------------------------------------------------------------------ 20 | 21 | bool AllocateImage(ImageMemoryDesc* const image, int32 width, int32 height, 22 | int num_channels, int32 bit_depth) { 23 | if (image == nullptr) { 24 | LOG("/!\\ Source is null."); 25 | return false; 26 | } 27 | int16 result = noErr; 28 | if (image->pixels.data == nullptr || (image->width != width) || 29 | (image->height != height) || (image->num_channels != num_channels)) { 30 | DeallocateImage(image); 31 | image->width = width; 32 | image->height = height; 33 | image->num_channels = num_channels; 34 | image->mode = plugInModeRGBColor; 35 | image->pixels.depth = bit_depth; 36 | image->pixels.colBits = image->pixels.depth * image->num_channels; 37 | image->pixels.rowBits = image->pixels.colBits * width; 38 | const size_t image_data_size = (size_t)(image->pixels.rowBits / 8) * height; 39 | Allocate(image_data_size, &image->pixels.data, &result); 40 | } 41 | return result == noErr; 42 | } 43 | 44 | void DeallocateImage(ImageMemoryDesc* const image) { 45 | if (image == nullptr) { 46 | LOG("/!\\ Source is null."); 47 | return; 48 | } 49 | Deallocate(&image->pixels.data); 50 | } 51 | 52 | void DeallocateMetadata(Metadata metadata[Metadata::kNum]) { 53 | for (int i = 0; i < Metadata::kNum; ++i) { 54 | WebPDataClear(&metadata[i].chunk); 55 | metadata[i].chunk.size = 0; // Extra safety in case NULL but size>0. 56 | } 57 | } 58 | 59 | //------------------------------------------------------------------------------ 60 | 61 | void ResizeFrameVector(std::vector* const frames, 62 | size_t size) { 63 | if (frames == nullptr) { 64 | LOG("/!\\ Source is null."); 65 | return; 66 | } 67 | for (size_t i = size; i < frames->size(); ++i) { 68 | DeallocateImage(&(*frames)[i].image); 69 | } 70 | frames->resize(size); 71 | } 72 | void ClearFrameVector(std::vector* const frames) { 73 | ResizeFrameVector(frames, 0); 74 | } 75 | 76 | //------------------------------------------------------------------------------ 77 | 78 | bool Scale(const ImageMemoryDesc& src, ImageMemoryDesc* const dst, 79 | size_t dst_width, size_t dst_height) { 80 | if (src.pixels.data == nullptr || src.width < 1 || src.height < 1 || 81 | (src.pixels.depth % 8) != 0 || dst == nullptr || &src == dst) { 82 | LOG("/!\\ Invalid source or destination."); 83 | return false; 84 | } 85 | if (!AllocateImage(dst, (int32)dst_width, (int32)dst_height, 86 | src.num_channels, src.pixels.depth)) { 87 | LOG("/!\\ AllocateImage failed."); 88 | return false; 89 | } 90 | dst->mode = src.mode; 91 | 92 | for (size_t dst_x = 0; dst_x < dst_width; ++dst_x) { 93 | const size_t src_x = dst_x * src.width / dst_width; 94 | for (size_t dst_y = 0; dst_y < dst_height; ++dst_y) { 95 | const size_t src_y = dst_y * src.height / dst_height; 96 | for (int channel = 0; channel < dst->num_channels; ++channel) { 97 | const uint8_t* src_data = 98 | reinterpret_cast(src.pixels.data) + 99 | src_x * (src.pixels.colBits / 8) + 100 | src_y * (src.pixels.rowBits / 8) + channel * (src.pixels.depth / 8); 101 | uint8_t* dst_data = reinterpret_cast(dst->pixels.data) + 102 | dst_x * (dst->pixels.colBits / 8) + 103 | dst_y * (dst->pixels.rowBits / 8) + 104 | channel * (dst->pixels.depth / 8); 105 | // No interpolation. 106 | std::copy(src_data, src_data + (dst->pixels.depth / 8), dst_data); 107 | } 108 | } 109 | } 110 | return true; 111 | } 112 | 113 | bool Crop(const ImageMemoryDesc& src, ImageMemoryDesc* const dst, 114 | size_t crop_width, size_t crop_height, size_t crop_left, 115 | size_t crop_top) { 116 | if (src.pixels.data == nullptr || src.width < 1 || src.height < 1 || 117 | (src.pixels.depth % 8) != 0 || dst == nullptr || &src == dst) { 118 | LOG("/!\\ Invalid source or destination."); 119 | return false; 120 | } 121 | if (crop_width < 1 || crop_height < 1 || 122 | crop_left + crop_width > (size_t)src.width || 123 | crop_top + crop_height > (size_t)src.height) { 124 | LOG("/!\\ Invalid input."); 125 | return false; 126 | } 127 | if (!AllocateImage(dst, (int32)crop_width, (int32)crop_height, 128 | src.num_channels, src.pixels.depth)) { 129 | LOG("/!\\ AllocateImage failed."); 130 | return false; 131 | } 132 | dst->mode = src.mode; 133 | 134 | for (size_t dst_x = 0; dst_x < crop_width; ++dst_x) { 135 | const size_t src_x = crop_left + dst_x; 136 | for (size_t dst_y = 0; dst_y < crop_height; ++dst_y) { 137 | const size_t src_y = crop_top + dst_y; 138 | for (int channel = 0; channel < dst->num_channels; ++channel) { 139 | const uint8_t* src_data = 140 | reinterpret_cast(src.pixels.data) + 141 | src_x * (src.pixels.colBits / 8) + 142 | src_y * (src.pixels.rowBits / 8) + channel * (src.pixels.depth / 8); 143 | uint8_t* dst_data = reinterpret_cast(dst->pixels.data) + 144 | dst_x * (dst->pixels.colBits / 8) + 145 | dst_y * (dst->pixels.rowBits / 8) + 146 | channel * (dst->pixels.depth / 8); 147 | std::copy(src_data, src_data + (dst->pixels.depth / 8), dst_data); 148 | } 149 | } 150 | } 151 | return true; 152 | } 153 | 154 | bool To8bit(const ImageMemoryDesc& src, bool add_alpha, 155 | ImageMemoryDesc* const dst) { 156 | if (src.width < 1 || src.height < 1 || dst == nullptr || 157 | (add_alpha && src.num_channels != 3) || &src == dst) { 158 | LOG("/!\\ Invalid source or destination."); 159 | return false; 160 | } 161 | if (src.pixels.depth != 8 && src.pixels.depth != 16 && 162 | src.pixels.depth != 32) { 163 | LOG("/!\\ Unsupported source depth " << src.pixels.depth << "."); 164 | return false; 165 | } 166 | 167 | if (!AllocateImage(dst, src.width, src.height, 168 | src.num_channels + (add_alpha ? 1 : 0), /*depth=*/8)) { 169 | LOG("/!\\ AllocateImage failed."); 170 | return false; 171 | } 172 | 173 | // Not much documentation was found besides SDK FAQ for 16b but it seems that: 174 | // Photoshop Image Mode | 8 Bits/Channel | 16 Bits/Channel | 32 Bits/Channel 175 | // ---------------------|----------------|-----------------|---------------- 176 | // RGB Color | [0:255] | [0:32768] | [0.f:1.f] 177 | 178 | for (size_t y = 0; y < src.height; ++y) { 179 | for (size_t x = 0; x < src.width; ++x) { 180 | for (int channel = 0; channel < dst->num_channels; ++channel) { 181 | uint8_t* dst_data = reinterpret_cast(dst->pixels.data) + 182 | x * (dst->pixels.colBits / 8) + 183 | y * (dst->pixels.rowBits / 8) + 184 | channel * (dst->pixels.depth / 8); 185 | if (channel >= src.num_channels) { 186 | *dst_data = 255; // add_alpha 187 | continue; 188 | } 189 | 190 | const uint8_t* src_data = 191 | reinterpret_cast(src.pixels.data) + 192 | x * (src.pixels.colBits / 8) + y * (src.pixels.rowBits / 8) + 193 | channel * (src.pixels.depth / 8); 194 | if (src.pixels.depth == 8) { 195 | *dst_data = *src_data; 196 | } else if (src.pixels.depth == 16) { 197 | uint32_t value = *reinterpret_cast(src_data); 198 | value >>= 7; 199 | *dst_data = (value >= 255) ? 255 : static_cast(value); 200 | } else { 201 | float value = *reinterpret_cast(src_data) * 255.f; 202 | *dst_data = 203 | (value >= 255.f) ? 255 : static_cast(std::lrint(value)); 204 | } 205 | } 206 | } 207 | } 208 | return true; 209 | } 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebP file format plug-in for Photoshop 2 | 3 | Current plug-in version: WebPShop 0.4.3 4 | 5 | **Disclaimer: WebPShop is no longer maintained.** 6 | 7 | WebPShop is a Photoshop module for opening and saving WebP images, including 8 | animations. 9 | 10 | **Important note: [Photoshop 23.2 and newer natively supports WebP](https://helpx.adobe.com/photoshop/kb/support-webp-image-format.html).** 11 | However some features such as preview at encoding and animations are missing 12 | in Photoshop 23.2. WebPShop can still be installed and used for these use 13 | cases or for Photoshop 23.1 and some earlier versions. 14 | 15 | Please look at the files LICENSE and CONTRIBUTING in the "docs" folder before 16 | using the contents of this repository or contributing. 17 | 18 | ## Installation 19 | 20 | Download the binary at https://github.com/webmproject/WebPShop/releases. \ 21 | Direct link for Windows x64: 22 | https://github.com/webmproject/WebPShop/releases/download/v0.4.3/WebPShop_0_4_3_Win_x64.8bi \ 23 | Direct link for MacOS (extract the ZIP archive afterwise): 24 | https://github.com/webmproject/WebPShop/releases/download/v0.4.2/WebPShop_0_4_2_Mac_Universal.zip \ 25 | Move the plug-in (the .8bi binary for Windows or the .plugin folder for MacOS) 26 | to the Photoshop plug-in directory 27 | (`C:\Program Files\Common Files\Adobe\Plug-Ins\CC` for Windows, 28 | `/Library/Application Support/Adobe/Plug-Ins/CC` for Mac). Run Photoshop. 29 | 30 | On macOS 10.15+, the prompt "WebPShop.plugin cannot be opened because 31 | the developer cannot be verified" can be bypassed by running the following 32 | in Terminal (Finder > Applications > Utilities): 33 | 34 | ``` 35 | sudo xattr -r -d com.apple.quarantine /Library/Application\ Support/Adobe/Plug-Ins/CC/WebPShop.plugin 36 | ``` 37 | 38 | ## Features 39 | 40 | * `Open`, `Open As` menu commands can be used to read .webp files. 41 | * `Save a Copy...` menu command can be used to write .webp files. Encoding 42 | parameters can be tuned through the UI. 43 | 44 | ![WebPShop encoding settings - Windows](docs/webpshop_enc_ui_windows.webp) 45 | 46 | Photoshop 23.2 and above has partial native WebP support. It will appear as 47 | `WebP (*.WEBP)` in the "Save as type:" drop-down list at encoding. WebPShop will 48 | still appear as `WebPShop (*.WEBP, *.WEBP)`. Use the latter. \ 49 | WebPShop can also be used at decoding through the "Open as..." menu. 50 | 51 | ## Encoding settings 52 | 53 | For information, the quality slider maps the following ranges to their internal 54 | WebP counterparts (see SetWebPConfig() in WebPShopEncodeUtils.cpp): 55 | 56 | | Quality slider value -> | 0 ... 97 | 98 99 | 100 | 57 | |---------------------------|----------------|---------------|----------| 58 | | WebP encoding settings -> | Lossy, quality | Near-lossless | Lossless | 59 | | | 0 ... 100 | 60 80 | | 60 | 61 | The radio buttons offer several levels of compression effort: 62 | 63 | | Label | WebP speed setting | Sharp YUV | WebP "quality" setting | 64 | | | | (lossy only) | (except for lossy) | 65 | |---------|---------------------|--------------|------------------------| 66 | | Fastest | 1 | No | 0 | 67 | | Default | 4 | No | 75 | 68 | | Slowest | 6 | Yes | 100 | 69 | 70 | Animations are supported through a specific layer naming pattern: 71 | 72 | * `FrameX (123 ms)` (last frame in chronological order) 73 | * ... 74 | * `Frame3 (1111 ms)` (third frame, shown for 1.111 seconds) 75 | * `Frame2 (321 ms)` (second frame, shown for 0.321 seconds) 76 | * `Frame1 (2000 ms)` (first frame, shown during the first two seconds of the 77 | animation) 78 | 79 | All layers must be rasterized, of the same dimensions, and have no filter, mask, 80 | group, link, etc. Please see the 81 | [detailed guidelines](https://github.com/webmproject/WebPShop/issues/60#issue-2237550279). 82 | 83 | ## Limitations 84 | 85 | * Only English is currently supported. 86 | * Only "RGB Color" image mode is currently supported. 87 | * 16 and 32 bits/channels are downscaled to 8 bits/channels before the WebP 88 | encoding because WebP only supports 8-bit internally. 89 | Exports from 32-bit documents should include the color profile in the WebP 90 | encoding settings, otherwise they might appear darker than expected. 91 | * WebP images cannot exceed 16383 x 16383 pixels. 92 | * The Timeline data is not used; thus animations rely on layers for defining 93 | frames (set duration as "(123 ms)" in each layer's name), and they need to 94 | be rasterized before saving. 95 | * On some images, lossless compression might produce smaller file sizes than 96 | lossy. That's why the quality slider is not linear. The same problem exists 97 | with the radio buttons controlling the compression effort. 98 | * The color profile is not applied to the Preview image on macOS, regardless 99 | of the related checkbox state. 100 | * This plug-in does not extend `Export As` neither `Save for Web`. 101 | * Encoding and decoding are done in a single pass. It is not currently 102 | possible to cancel such actions, and it might take some time on big images. 103 | 104 | ## Troubleshooting 105 | 106 | If the plug-in is not detected or does not behave as expected, the steps below 107 | might help: 108 | 109 | * Update Photoshop to the latest version. 110 | * Double-check that the plug-in binaries match the Operating System and the 111 | architecture. 112 | * The plug-in should be listed in the "Help > About Plugins" submenu if it is 113 | found by Photoshop. 114 | * If it is undetected, disable any antivirus program or allow the plug-in 115 | execution (including in MacOS and Windows built-in protections). 116 | * If it is still undetected, try each of these folders. Windows paths: 117 | 118 | C:\Program Files\Common Files\Adobe\Plug-Ins\CC 119 | C:\Program Files\Common Files\Adobe\Plug-Ins\CC\File Formats 120 | C:\Program Files\Adobe\Adobe Photoshop 2022\Plug-ins 121 | 122 | MacOS paths: 123 | 124 | /Library/Application Support/Adobe/Plug-Ins/CC 125 | Applications/Adobe Photoshop/Plug-ins/ 126 | 127 | * If it is still undetected, remove all plug-ins from all folders and copy 128 | WebPShop in only one of these folders, in case there is a plug-in conflict. 129 | Restart the computer and/or Photoshop. 130 | 131 | If the issue still occurs, check https://github.com/webmproject/WebPShop/issues 132 | to see if it is already mentioned or open a new bug report otherwise. 133 | 134 | ## Software architecture 135 | 136 | The `common` folder contains the following: 137 | 138 | * `WebPShop.h` is the main header, containing most functions. 139 | * `WebPShop.cpp` contains the plug-in entry point (called by host). 140 | * `WebPShop.r` and `WebPShopTerminology.h` represent the plug-in properties. 141 | * Functions in `WebPShopSelector*` are called in `WebPShop.cpp`. 142 | * `WebPShop*Utils.cpp` are helper functions. 143 | * `WebPShopScripting.cpp` is mostly used for automation. 144 | * `WebPShopUI*` display the encoding parameters window and the About box. 145 | 146 | The `win` folder contains a Visual Studio solution and project, alongside with 147 | `WebPShop.rc` which is the encoding parameters window layout and About box. 148 | 149 | The `mac` folder contains an XCode project. `WebPShopUIDialog_mac.h` and `.mm` 150 | describe the UI layout, while `WebPShopUI_mac.mm` handles the window events. 151 | 152 | ## Build 153 | 154 | Current libwebp version: WebP 1.2.2 155 | 156 | Use Microsoft Visual Studio (2019 and above) for Windows and XCode for Mac. 157 | 158 | * Download the latest Adobe Photoshop Plug-In and Connection SDK at 159 | https://console.adobe.io/downloads/ps, 160 | * Put the contents of this repository in a "webpshop" folder located at 161 | `adobe_photoshop_sdk_[version]/pluginsdk/samplecode/format`, 162 | * Download the latest WebP binaries at 163 | https://developers.google.com/speed/webp/docs/precompiled or build them, 164 | * Add `path/to/webp/includes` and `path/to/webp/includes/src` as Additional 165 | Include Directories to the WebPShop project [1], 166 | * Add webp, webpdemux, webpmux libraries as Additional Dependencies to the 167 | WebPShop project [1], 168 | * Build with the same architecture and configuration as your Photoshop 169 | installation and the WebP binaries (x64 or arm64, Debug/Release), 170 | * By example for Windows, it should output the plug-in file `WebPShop.8bi` in 171 | `adobe_photoshop_sdk_[version]/pluginsdk/samplecode/Output/Win/x64/Release`. 172 | 173 | [1] By default the XCode project includes and links to the 174 | `libwebp-[version]-mac-[version]` folder in the `webpshop` directory. The VS 175 | project expects `libwebp-[version]-windows-x64` (or `-arm64`). 176 | -------------------------------------------------------------------------------- /common/WebPShop.r: -------------------------------------------------------------------------------- 1 | // # Copyright 2018 Google LLC 2 | // # 3 | // # Licensed under the Apache License, Version 2.0 (the "License"); 4 | // # you may not use this file except in compliance with the License. 5 | // # You may obtain a copy of the License at 6 | // # 7 | // # https://www.apache.org/licenses/LICENSE-2.0 8 | // # 9 | // # Unless required by applicable law or agreed to in writing, software 10 | // # distributed under the License is distributed on an "AS IS" BASIS, 11 | // # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // # See the License for the specific language governing permissions and 13 | // # limitations under the License. 14 | // (Symbol # is used in case this file is wrongly recognized as R language) 15 | 16 | //------------------------------------------------------------------------------ 17 | // Definitions -- Required by include files. 18 | //------------------------------------------------------------------------------ 19 | 20 | #define plugInName "WebPShop" 21 | #define plugInCopyrightYear "2020" 22 | #define plugInDescription \ 23 | "A Photoshop plug-in for reading and writing WebP files." 24 | 25 | //------------------------------------------------------------------------------ 26 | // Definitions -- Required by other resources in this resources file. 27 | //------------------------------------------------------------------------------ 28 | 29 | // Dictionary (aete) resources: 30 | 31 | #define webpshopResourceID 16000 // number seems standard 32 | #define vendorName "Google" 33 | #define plugInAETEComment "webp format module" 34 | 35 | #define plugInSuiteID 'gOOG' 36 | #define plugInClassID 'webP' 37 | #define plugInEventID typeNull // must be this 38 | 39 | //------------------------------------------------------------------------------ 40 | // Set up included files for Macintosh and Windows. 41 | //------------------------------------------------------------------------------ 42 | 43 | #include "PIDefines.h" 44 | #include "PIResourceDefines.h" 45 | 46 | #if __PIMac__ 47 | #include "PIGeneral.r" 48 | #include "PIUtilities.r" 49 | #elif defined(__PIWin__) 50 | #define Rez 51 | #include "PIGeneral.h" 52 | #include "PIUtilities.r" 53 | #endif 54 | 55 | #include "PIActions.h" 56 | #include "PITerminology.h" 57 | 58 | #include "WebPShopTerminology.h" 59 | 60 | //------------------------------------------------------------------------------ 61 | // PiPL resource 62 | //------------------------------------------------------------------------------ 63 | 64 | resource 'PiPL'(webpshopResourceID, plugInName " PiPL", purgeable){ 65 | {Kind{ImageFormat}, Name{plugInName}, 66 | Version{(latestFormatVersion << 16) | latestFormatSubVersion}, 67 | 68 | Component{ComponentNumber, plugInName}, 69 | 70 | #if Macintosh 71 | #if defined(__arm64__) 72 | CodeMacARM64 { "PluginMain" }, 73 | #endif 74 | #if defined(__x86_64__) 75 | CodeMacIntel64 { "PluginMain" }, 76 | #endif 77 | #elif MSWindows 78 | CodeEntryPointWin64 { "PluginMain" }, 79 | #endif 80 | 81 | // This plug-in can read and write via POSIX I/O routines 82 | SupportsPOSIXIO{}, 83 | 84 | // ClassID, eventID, aete ID, uniqueString: 85 | HasTerminology{plugInClassID, plugInEventID, webpshopResourceID, 86 | vendorName " " plugInName}, 87 | 88 | // Show or hide the format when saving in a given Image Mode. 89 | // Always show but display an error in DoOptionsStart() if not RGB. 90 | SupportedModes{doesSupportBitmap, doesSupportGrayScale, 91 | doesSupportIndexedColor, doesSupportRGBColor, 92 | doesSupportCMYKColor, doesSupportHSLColor, 93 | doesSupportHSBColor, doesSupportMultichannel, 94 | doesSupportDuotone, doesSupportLABColor}, 95 | // "EnableInfo and SupportedModes should always match" according to 96 | // PIGeneral.h File Reference, but SimpleFormat.r just uses "true". 97 | EnableInfo{"true"}, 98 | 99 | // WebP is limited to 16383 in width and height. 100 | PlugInMaxSize{16383, 16383}, FormatMaxSize{{16383, 16383}}, 101 | 102 | FormatMaxChannels{{1, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24}}, 103 | 104 | FmtFileType{'WEBP', '8BIM'}, 105 | ReadTypes{{'WEBP', ' '}}, 106 | // FilteredTypes{{'8B1F', ' '}}, 107 | ReadExtensions{{'WEBP'}}, 108 | WriteExtensions{{'WEBP'}}, 109 | FilteredExtensions{{'WEBP'}}, 110 | FormatFlags{fmtSavesImageResources, fmtCanRead, fmtCanWrite, 111 | fmtCanWriteIfRead, fmtCanWriteTransparency, 112 | fmtCannotCreateThumbnail}, 113 | FormatICCFlags{iccCannotEmbedGray, iccCannotEmbedIndexed, 114 | iccCanEmbedRGB, iccCannotEmbedCMYK}, 115 | 116 | // XMPRead and XMPWrite properties would enable formatSelectorXMPRead 117 | // and formatSelectorXMPWrite which represent another way of handling 118 | // XMP metadata import/export. Currently it is done as EXIF: through 119 | // handle allocation and setPropertyProc(). 120 | 121 | // No way of enabling the "ICC Profile" checkbox in the "Save As" window 122 | // was found. Currently it is configurable in the "WebPShop" window among 123 | // other WebP settings and internally handled in a similar way as EXIF and 124 | // XMP chunks, using the iCCprofileData handle. 125 | 126 | // Layer support is needed for reading animated WebPs into layers 127 | // (also it would be better to also load it into the timeline). 128 | FormatLayerSupport{doesSupportFormatLayers}, 129 | // However it is not needed for writing because layer data is directly 130 | // fetched with CopyAllLayers() thus there is no need to wait for 131 | // Photoshop and DoWriteLayer*(). Also with FormatLayerSupportReadOnly 132 | // Ctrl+S defaults to .psd instead of .webp, which is the default 133 | // behavior for .png etc. 134 | FormatLayerSupportReadOnly{}}}; 135 | 136 | //------------------------------------------------------------------------------ 137 | // Dictionary (scripting) resource 138 | //------------------------------------------------------------------------------ 139 | 140 | resource 'aete'(webpshopResourceID, plugInName " dictionary", purgeable){ 141 | 1, 142 | 0, 143 | english, 144 | roman, /* aete version and language specifiers */ 145 | { 146 | vendorName, /* vendor suite name */ 147 | "WebP format plug-in", /* optional description */ 148 | plugInSuiteID, /* suite ID */ 149 | 1, /* suite code, must be 1 */ 150 | 1, /* suite level, must be 1 */ 151 | {}, /* structure for filters */ 152 | { 153 | /* non-filter plug-in class here */ 154 | vendorName " WebP format", /* unique class name */ 155 | plugInClassID, /* class ID, must be unique or Suite ID */ 156 | plugInAETEComment, /* optional description */ 157 | { /* define inheritance */ 158 | "", /* must be exactly this */ 159 | keyInherits, /* must be keyInherits */ 160 | classFormat, /* parent: Format, Import, Export */ 161 | "parent class format", /* optional description */ 162 | flagsSingleProperty, /* if properties, list below */ 163 | 164 | "Quality", 165 | keyWriteConfig_quality, 166 | typeInteger, 167 | "image quality after compression", 168 | flagsSingleProperty, 169 | 170 | "Compression", 171 | keyWriteConfig_compression, 172 | typeInteger, 173 | "compression level", 174 | flagsSingleProperty, 175 | 176 | "Keep EXIF", 177 | keyWriteConfig_keep_exif, 178 | typeBoolean, 179 | "keep EXIF metadata", 180 | flagsSingleProperty, 181 | 182 | "Keep XMP", 183 | keyWriteConfig_keep_xmp, 184 | typeBoolean, 185 | "keep XMP metadata", 186 | flagsSingleProperty, 187 | 188 | "Keep Color Profile", 189 | keyWriteConfig_keep_color_profile, 190 | typeBoolean, 191 | "keep color profile", 192 | flagsSingleProperty, 193 | 194 | "Infinite Loop", 195 | keyWriteConfig_loop_forever, 196 | typeBoolean, 197 | "loop the animation forever", 198 | flagsSingleProperty, 199 | 200 | "Using POSIX I/O", 201 | keyUsePOSIX, 202 | typeBoolean, 203 | "use POSIX file i/o", 204 | flagsSingleProperty}, 205 | {}, /* elements (not supported) */ 206 | /* class descriptions */ 207 | }, 208 | {}, /* comparison ops (not supported) */ 209 | {} /* any enumerations */ 210 | }}; 211 | 212 | //------------------------------------------------------------------------------ 213 | // History resource 214 | //------------------------------------------------------------------------------ 215 | 216 | resource 'STR '(kHistoryEntry, "History", purgeable){ 217 | plugInName ": ref num=^0." 218 | }; 219 | 220 | //------------------------------------------------------------------------------ 221 | 222 | // end WebPShop.r 223 | -------------------------------------------------------------------------------- /common/WebPShop.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "WebPShop.h" 16 | 17 | #include 18 | 19 | #include "PIFormat.h" 20 | #include "WebPShopSelector.h" 21 | 22 | DLLExport MACPASCAL void PluginMain(const int16 selector, 23 | FormatRecordPtr formatParamBlock, 24 | intptr_t* dataHandle, int16* result); 25 | 26 | SPBasicSuite* sSPBasic = NULL; // External reference for resource allocation. 27 | 28 | static bool TryAcquireSuite(Data* const data, int16* const result) { 29 | if (sSPBasic->AcquireSuite(kPSChannelPortsSuite, kPSChannelPortsSuiteVersion3, 30 | (const void**)&data->sPSChannelPortsSuite) || 31 | data->sPSChannelPortsSuite == nullptr) { 32 | LOG("/!\\ Unable to acquire suite."); 33 | *result = errPlugInHostInsufficient; 34 | return false; 35 | } 36 | return true; 37 | } 38 | 39 | //------------------------------------------------------------------------------ 40 | // 41 | // PluginMain / main (description from Adobe SDK) 42 | // 43 | // All calls to the plug-in module come through this routine. It must be placed 44 | // first in the resource. To achieve this, most development systems require this 45 | // be the first routine in the source. 46 | // 47 | // The entrypoint will be "pascal void" for Macintosh, "void" for Windows. 48 | // 49 | // Parameters: 50 | // const int16 selector 51 | // Host provides selector indicating what command to do. 52 | // FormatRecordPtr formatParamBlock 53 | // Host provides pointer to parameter block containing pertinent data and 54 | // callbacks from the host. See PIFormat.h. 55 | // intptr_t* dataPointer 56 | // Use this to store a pointer to our global parameters structure, 57 | // which is maintained by the host between calls to the plug-in. 58 | // int16* result 59 | // Return error result or noErr. Some errors are handled by the host, some 60 | // are silent, and some you must handle. See PIGeneral.h. 61 | // 62 | //------------------------------------------------------------------------------ 63 | 64 | DLLExport MACPASCAL void PluginMain(const int16 selector, 65 | FormatRecordPtr formatParamBlock, 66 | intptr_t* dataPointer, int16* result) { 67 | try { 68 | // The about box is a special request; the parameter block is not filled 69 | // out, none of the callbacks or standard data is available. Instead, the 70 | // parameter block points to an AboutRecord, which is used on Windows. 71 | if (selector == formatSelectorAbout) { 72 | AboutRecordPtr aboutRecord = 73 | reinterpret_cast(formatParamBlock); 74 | sSPBasic = aboutRecord->sSPBasic; 75 | SPPluginRef plugin_ref = 76 | reinterpret_cast(aboutRecord->plugInRef); 77 | DoAboutBox(plugin_ref); 78 | } else { 79 | FormatRecordPtr format_record = formatParamBlock; 80 | sSPBasic = format_record->sSPBasic; 81 | SPPluginRef plugin_ref = 82 | reinterpret_cast(format_record->plugInRef); 83 | 84 | // For Photoshop 8, big documents, rows and columns are now > 30000 px. 85 | if (format_record->HostSupports32BitCoordinates) { 86 | format_record->PluginUsing32BitCoordinates = true; 87 | } 88 | 89 | // Allocate and initialize data. 90 | Data* data = (Data*)*dataPointer; 91 | if (data == nullptr) { 92 | LOG(std::endl << "Initialization."); 93 | data = (Data*)malloc(sizeof(Data)); 94 | if (data == nullptr) { 95 | LOG("/!\\ Unable to allocate " << sizeof(Data) << " bytes."); 96 | *result = memFullErr; 97 | return; 98 | } 99 | 100 | // Photoshop should take care of freeing it. 101 | *dataPointer = (intptr_t)data; 102 | 103 | // Init data. 104 | WebPInitDecoderConfig(&data->read_config); 105 | data->write_config.quality = 75; 106 | data->write_config.compression = Compression::DEFAULT; 107 | data->write_config.keep_exif = false; 108 | data->write_config.keep_xmp = false; 109 | data->write_config.keep_color_profile = false; 110 | data->write_config.loop_forever = true; 111 | data->write_config.animation = false; 112 | data->write_config.display_proxy = false; 113 | data->file_size = 0; 114 | data->file_data = nullptr; 115 | data->metadata[Metadata::kEXIF].four_cc = "EXIF"; 116 | data->metadata[Metadata::kXMP].four_cc = "XMP "; 117 | data->metadata[Metadata::kICCP].four_cc = "ICCP"; 118 | for (Metadata& metadata : data->metadata) WebPDataInit(&metadata.chunk); 119 | WebPDataInit(&data->encoded_data); 120 | data->anim_decoder = nullptr; 121 | data->last_frame_timestamp = 0; 122 | data->sPSChannelPortsSuite = nullptr; 123 | 124 | if (!TryAcquireSuite(data, result)) return; 125 | } 126 | 127 | // Dispatch selector. 128 | switch (selector) { 129 | case formatSelectorReadPrepare: 130 | DoReadPrepare(format_record, data, result); 131 | break; 132 | case formatSelectorReadStart: 133 | DoReadStart(format_record, data, result); 134 | break; 135 | case formatSelectorReadContinue: 136 | DoReadContinue(format_record, data, result); 137 | break; 138 | case formatSelectorReadFinish: 139 | DoReadFinish(format_record, data, result); 140 | break; 141 | 142 | case formatSelectorOptionsPrepare: 143 | DoOptionsPrepare(format_record, data, result); 144 | break; 145 | case formatSelectorOptionsStart: 146 | DoOptionsStart(format_record, data, result, plugin_ref); 147 | break; 148 | case formatSelectorOptionsContinue: 149 | DoOptionsContinue(format_record, data, result); 150 | break; 151 | case formatSelectorOptionsFinish: 152 | DoOptionsFinish(format_record, data, result); 153 | break; 154 | 155 | case formatSelectorEstimatePrepare: 156 | DoEstimatePrepare(format_record, data, result); 157 | break; 158 | case formatSelectorEstimateStart: 159 | DoEstimateStart(format_record, data, result); 160 | break; 161 | case formatSelectorEstimateContinue: 162 | DoEstimateContinue(format_record, data, result); 163 | break; 164 | case formatSelectorEstimateFinish: 165 | DoEstimateFinish(format_record, data, result); 166 | break; 167 | 168 | case formatSelectorWritePrepare: 169 | DoWritePrepare(format_record, data, result); 170 | break; 171 | case formatSelectorWriteStart: 172 | DoWriteStart(format_record, data, result); 173 | break; 174 | case formatSelectorWriteContinue: 175 | DoWriteContinue(format_record, data, result); 176 | break; 177 | case formatSelectorWriteFinish: 178 | DoWriteFinish(format_record, data, result); 179 | break; 180 | 181 | case formatSelectorReadLayerStart: 182 | DoReadLayerStart(format_record, data, result); 183 | break; 184 | case formatSelectorReadLayerContinue: 185 | DoReadLayerContinue(format_record, data, result); 186 | break; 187 | case formatSelectorReadLayerFinish: 188 | DoReadLayerFinish(format_record, data, result); 189 | break; 190 | 191 | case formatSelectorWriteLayerStart: 192 | DoWriteLayerStart(format_record, data, result); 193 | break; 194 | case formatSelectorWriteLayerContinue: 195 | DoWriteLayerContinue(format_record, data, result); 196 | break; 197 | case formatSelectorWriteLayerFinish: 198 | DoWriteLayerFinish(format_record, data, result); 199 | break; 200 | 201 | case formatSelectorFilterFile: 202 | DoFilterFile(format_record, data, result); 203 | break; 204 | } 205 | } 206 | 207 | // Release any suites that we may have acquired. 208 | if (selector == formatSelectorAbout || 209 | selector == formatSelectorWriteFinish || 210 | selector == formatSelectorReadFinish || 211 | selector == formatSelectorOptionsFinish || 212 | selector == formatSelectorEstimateFinish || 213 | selector == formatSelectorFilterFile || *result != noErr) { 214 | PIUSuitesRelease(); 215 | } 216 | 217 | } catch (const std::exception& e) { 218 | (void)e; 219 | LOG("/!\\ Exception: " << e.what()); 220 | if (result != nullptr) *result = -1; 221 | } catch (...) { 222 | LOG("/!\\ Caught an unknown exception."); 223 | if (result != nullptr) *result = -1; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /win/WebPShopUI_windows.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "PIUI.h" 16 | 17 | #ifdef __PIWin__ 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "WebPShopUI.h" 25 | 26 | //------------------------------------------------------------------------------ 27 | 28 | static RECT VRectToRECT(const VRect& rect) { 29 | RECT r; 30 | r.left = rect.left; 31 | r.right = rect.right; 32 | r.top = rect.top; 33 | r.bottom = rect.bottom; 34 | return r; 35 | } 36 | 37 | //------------------------------------------------------------------------------ 38 | 39 | void PISlider::SetItem(PIDialogPtr dialog, int16 item_id, int min, int max) { 40 | item_ = PIGetDialogItem(dialog, item_id); 41 | SendMessage(item_, (UINT)TBM_SETRANGEMIN, (WPARAM)(BOOL)FALSE, (LPARAM)min); 42 | SendMessage(item_, (UINT)TBM_SETRANGEMAX, (WPARAM)(BOOL)FALSE, (LPARAM)max); 43 | } 44 | void PISlider::SetValue(int value) { 45 | SendMessage(item_, (UINT)TBM_SETPOS, (WPARAM)(BOOL)TRUE, (LPARAM)value); 46 | } 47 | int PISlider::GetValue(void) { 48 | return (int)SendMessage(item_, (UINT)TBM_GETPOS, (WPARAM)0, (LPARAM)0); 49 | } 50 | 51 | //------------------------------------------------------------------------------ 52 | 53 | void PIIntegerField::SetItem(PIDialogPtr dialog, int16 item_id, int min, 54 | int max) { 55 | dialog_ = dialog; 56 | item_id_ = item_id; 57 | min_ = min(min, max); 58 | max_ = max(min, max); 59 | } 60 | void PIIntegerField::SetValue(int value) { 61 | char txt[4] = {'\0', '\0', '\0', '\0'}; 62 | if (value < 0) { 63 | txt[0] = '0'; 64 | } else if (value < 10) { 65 | txt[0] = '0' + value % 10; 66 | } else if (value < 100) { 67 | txt[1] = '0' + value % 10; 68 | value /= 10; 69 | txt[0] = '0' + value % 10; 70 | } else { 71 | txt[2] = '0' + value % 10; 72 | value /= 10; 73 | txt[1] = '0' + value % 10; 74 | value /= 10; 75 | txt[0] = '0' + value % 10; 76 | } 77 | SetDlgItemText(dialog_, item_id_, txt); 78 | } 79 | int PIIntegerField::GetValue(void) { 80 | char txt[5]; 81 | if (GetDlgItemText(dialog_, item_id_, txt, 4)) { 82 | int value = 0; 83 | for (int i = 0; i < 3 && txt[i] >= '0' && txt[i] <= '9'; ++i) { 84 | value = value * 10 + txt[i] - '0'; 85 | } 86 | if (value >= min_ && value <= max_) return value; 87 | } 88 | return -1; 89 | } 90 | 91 | //------------------------------------------------------------------------------ 92 | 93 | PIItem WebPShopDialog::GetItem(short item) { 94 | return GetDlgItem(GetDialog(), item); 95 | } 96 | void WebPShopDialog::ShowItem(short item) { 97 | ShowWindow(GetItem(item), SW_SHOW); 98 | } 99 | void WebPShopDialog::HideItem(short item) { 100 | ShowWindow(GetItem(item), SW_HIDE); 101 | } 102 | void WebPShopDialog::EnableItem(short item) { 103 | EnableWindow(GetItem(item), TRUE); 104 | } 105 | void WebPShopDialog::DisableItem(short item) { 106 | EnableWindow(GetItem(item), FALSE); 107 | } 108 | 109 | //------------------------------------------------------------------------------ 110 | 111 | VRect WebPShopDialog::GetProxyAreaRectInWindow(void) { 112 | RECT proxy_rect_in_screen; 113 | GetWindowRect(GetDlgItem(GetDialog(), kDProxy), &proxy_rect_in_screen); 114 | POINT window_pos; 115 | window_pos.x = window_pos.y = 0; 116 | ClientToScreen(GetDialog(), &window_pos); 117 | 118 | VRect proxy_rect_in_window; 119 | proxy_rect_in_window.left = proxy_rect_in_screen.left - window_pos.x; 120 | proxy_rect_in_window.right = proxy_rect_in_screen.right - window_pos.x; 121 | proxy_rect_in_window.top = proxy_rect_in_screen.top - window_pos.y; 122 | proxy_rect_in_window.bottom = proxy_rect_in_screen.bottom - window_pos.y; 123 | return proxy_rect_in_window; 124 | } 125 | 126 | void WebPShopDialog::BeginPainting(PaintingContext* const painting_context) { 127 | painting_context->hDC = BeginPaint(GetDialog(), &painting_context->ps); 128 | } 129 | void WebPShopDialog::EndPainting(PaintingContext* const painting_context) { 130 | EndPaint(GetDialog(), (LPPAINTSTRUCT)&painting_context->ps); 131 | } 132 | void WebPShopDialog::ClearRect(const VRect& rect, 133 | PaintingContext* const painting_context) { 134 | RECT proxy_RECT = VRectToRECT(rect); 135 | FillRect(painting_context->hDC, &proxy_RECT, GetSysColorBrush(COLOR_MENU)); 136 | } 137 | void WebPShopDialog::DrawRectBorder(uint8_t r, uint8_t g, uint8_t b, 138 | int left, int top, int right, int bottom, 139 | PaintingContext* const painting_context) { 140 | HGDIOBJ original = 141 | SelectObject(painting_context->hDC, GetStockObject(HOLLOW_BRUSH)); 142 | SelectObject(painting_context->hDC, GetStockObject(DC_PEN)); 143 | SetDCPenColor(painting_context->hDC, RGB(r, g, b)); 144 | Rectangle(painting_context->hDC, left, top, right, bottom); 145 | SelectObject(painting_context->hDC, original); 146 | } 147 | 148 | //------------------------------------------------------------------------------ 149 | 150 | bool WebPShopDialog::DisplayImage(const ImageMemoryDesc& image, 151 | const VRect& rect, 152 | DisplayPixelsProc display_pixels_proc, 153 | PaintingContext* const painting_context) { 154 | if (display_pixels_proc == nullptr) { 155 | LOG("/!\\ No display function."); 156 | return false; 157 | } 158 | 159 | PSPixelMask mask = {}; 160 | PSPixelMap pixels = {}; 161 | pixels.version = 3; 162 | pixels.bounds.left = 0; 163 | pixels.bounds.right = image.width; 164 | pixels.bounds.top = 0; 165 | pixels.bounds.bottom = image.height; 166 | pixels.imageMode = image.mode; 167 | pixels.planeBytes = image.pixels.depth / 8; 168 | pixels.colBytes = image.pixels.colBits / 8; 169 | pixels.rowBytes = image.pixels.rowBits / 8; 170 | pixels.baseAddr = image.pixels.data; 171 | 172 | pixels.mat = NULL; 173 | pixels.masks = NULL; 174 | pixels.maskPhaseRow = 0; 175 | pixels.maskPhaseCol = 0; 176 | pixels.pixelOverlays = NULL; 177 | pixels.colorManagementOptions = write_config_.keep_color_profile 178 | ? kViewAsStandardRGB 179 | : kViewAsUncompensated; 180 | pixels.depth = image.pixels.depth; 181 | 182 | if (image.num_channels == 4) { 183 | mask.next = NULL; 184 | mask.maskData = (uint8_t*)image.pixels.data + (image.pixels.depth / 8) * 3; 185 | mask.colBytes = image.pixels.colBits / 8; 186 | mask.rowBytes = image.pixels.rowBits / 8; 187 | mask.maskDescription = kSimplePSMask; 188 | 189 | pixels.masks = &mask; 190 | } 191 | 192 | const OSErr err = 193 | display_pixels_proc(&pixels, &pixels.bounds, rect.top, rect.left, 194 | (void*)painting_context->hDC); 195 | if (err != noErr) { 196 | LOG("/!\\ DisplayPixelsProc() failed: " << err); 197 | return false; 198 | } 199 | return true; 200 | } 201 | 202 | //------------------------------------------------------------------------------ 203 | 204 | void WebPShopDialog::TriggerRepaint() { 205 | RECT imageRect; 206 | PIDialogPtr dialog = GetDialog(); 207 | GetWindowRect(GetDlgItem(dialog, kDProxy), &imageRect); 208 | ScreenToClient(dialog, (LPPOINT)&imageRect); 209 | ScreenToClient(dialog, (LPPOINT) & (imageRect.right)); 210 | InvalidateRect(dialog, &imageRect, FALSE); 211 | } 212 | 213 | DLLExport BOOL WINAPI WindowProc(HWND hDlg, UINT wMsg, WPARAM wParam, 214 | LPARAM lParam) { 215 | static WebPShopDialog* owner = NULL; 216 | 217 | switch (wMsg) { 218 | case WM_INITDIALOG: { 219 | CenterDialog(hDlg); 220 | owner = static_cast((void*)(lParam)); 221 | if (owner == NULL) return FALSE; 222 | owner->SetDialog(hDlg); 223 | owner->Init(); 224 | owner->PaintProxy(); 225 | return TRUE; 226 | } 227 | case WM_PAINT: { 228 | if (owner != NULL) owner->PaintProxy(); 229 | return FALSE; 230 | } 231 | case WM_COMMAND: { 232 | int item = LOWORD(wParam); 233 | int cmd = HIWORD(wParam); 234 | if (owner != NULL) owner->Notify(item); 235 | if ((item == kDOK || item == kDCancel) && cmd == BN_CLICKED) { 236 | EndDialog(hDlg, item); 237 | } 238 | return TRUE; 239 | } 240 | case WM_DESTROY: { 241 | if (owner != NULL) owner->DeallocateCompressedFrames(); 242 | owner = NULL; 243 | return FALSE; 244 | } 245 | case WM_LBUTTONDOWN: 246 | case WM_MOUSEMOVE: { 247 | int x = GET_X_LPARAM(lParam), y = GET_Y_LPARAM(lParam); 248 | if (owner != NULL) owner->OnMouseMove(x, y, wParam & MK_LBUTTON); 249 | return FALSE; 250 | } 251 | default: 252 | // Slider (trackbar) messages arrive here. 253 | int32 item = LOWORD(wParam); 254 | if (item == kDQualitySlider || item == kDFrameSlider) { 255 | if (owner != NULL) owner->Notify(item); 256 | } else { 257 | // DefWindowProc() does not bring any visible benefit but a weird focus 258 | // loss issue when clicking the X button. Do not call it. 259 | // DefWindowProc(hDlg, wMsg, wParam, lParam); 260 | } 261 | return FALSE; 262 | } 263 | } 264 | 265 | int WebPShopDialog::Modal(SPPluginRef plugin, const char*, int ID) { 266 | SetID(ID); 267 | SetPluginRef(plugin); 268 | 269 | const int itemHit = (int)(DialogBoxParam( 270 | GetDLLInstance(GetPluginRef()), MAKEINTRESOURCE(GetID()), 271 | GetActiveWindow(), (DLGPROC)WindowProc, (LPARAM)this)); 272 | return itemHit; 273 | } 274 | 275 | //------------------------------------------------------------------------------ 276 | // About box 277 | 278 | DLLExport BOOL WINAPI AboutBoxProc(HWND hDlg, UINT wMsg, WPARAM wParam, 279 | LPARAM lParam) { 280 | switch (wMsg) { 281 | case WM_INITDIALOG: { 282 | CenterDialog(hDlg); 283 | return TRUE; // Direct the system to set the keyboard focus to wParam. 284 | } 285 | case WM_COMMAND: { 286 | int item = LOWORD(wParam); 287 | if (item == kDAboutWebPLink) { 288 | sPSFileList->BrowseUrl("https://developers.google.com/speed/webp"); 289 | return TRUE; 290 | } 291 | return FALSE; 292 | } 293 | case WM_KEYDOWN: // Keyboard event (does not work) 294 | case WM_LBUTTONUP: // Mouse click on window 295 | case WM_CLOSE: { // Mouse click on [X] button 296 | EndDialog(hDlg, 0); 297 | return TRUE; 298 | } 299 | default: 300 | return FALSE; //Let the dialog manager perform the default operation. 301 | } 302 | } 303 | 304 | void DoAboutBox(SPPluginRef plugin_ref) { 305 | (void)DialogBoxParam(GetDLLInstance(plugin_ref), MAKEINTRESOURCE(16091), 306 | GetActiveWindow(), (DLGPROC)AboutBoxProc, (LPARAM)0); 307 | } 308 | 309 | #endif // __PIWin__ 310 | -------------------------------------------------------------------------------- /common/WebPShopEncodeUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include "FileUtilities.h" 18 | #include "PIProperties.h" 19 | #include "WebPShop.h" 20 | #include "webp/encode.h" 21 | #include "webp/mux.h" 22 | 23 | void SetWebPConfig(WebPConfig* const config, const WriteConfig& write_config) { 24 | if (write_config.quality < 0 || write_config.quality > 100) { 25 | LOG("/!\\ Bad write_config."); 26 | return; 27 | } 28 | 29 | const int near_lossless_starts_at = 98; 30 | if (write_config.quality >= near_lossless_starts_at) { 31 | config->lossless = 1; 32 | config->near_lossless = (write_config.quality == 98) ? 60 : 33 | (write_config.quality == 99) ? 80 : 34 | 100; 35 | if (write_config.compression == Compression::FASTEST) { 36 | config->quality = 0.0f; 37 | } else if (write_config.compression == Compression::DEFAULT) { 38 | config->quality = 75.0f; 39 | } else { 40 | config->quality = 100.0f; 41 | } 42 | } else { 43 | config->lossless = 0; 44 | config->quality = 45 | write_config.quality * 100.0f / (near_lossless_starts_at - 1); 46 | config->use_sharp_yuv = (write_config.compression == Compression::SLOWEST); 47 | } 48 | 49 | if (write_config.compression == Compression::FASTEST) { 50 | config->method = 1; 51 | } else if (write_config.compression == Compression::DEFAULT) { 52 | config->method = 4; 53 | } else { 54 | config->method = 6; 55 | } 56 | 57 | // Low alpha qualities are terrible on gradients: limit to acceptable range. 58 | config->alpha_quality = 50 + write_config.quality / 2; 59 | if (config->alpha_quality > 100) config->alpha_quality = 100; 60 | } 61 | 62 | bool CastToWebPPicture(const WebPConfig& config, const ImageMemoryDesc& src, 63 | WebPPicture* const dst) { 64 | WebPPictureFree(dst); 65 | 66 | if (src.pixels.data == nullptr || src.width < 1 || src.height < 1 || 67 | src.num_channels != 4 || src.pixels.depth != 8) { 68 | LOG("/!\\ Unsupported ImageMemoryDesc layout."); 69 | return false; 70 | } 71 | 72 | dst->use_argb = 1; 73 | dst->width = src.width; 74 | dst->height = src.height; 75 | 76 | // The data can be mapped to a WebPPicture without allocation. 77 | assert(!config.show_compressed); // 'dst->argb[]' will not be altered. 78 | dst->argb = const_cast( 79 | reinterpret_cast(src.pixels.data)); 80 | dst->argb_stride = src.width; 81 | return true; 82 | } 83 | 84 | bool EncodeOneImage(const ImageMemoryDesc& original_image, 85 | const WriteConfig& write_config, 86 | WebPData* const encoded_data) { 87 | START_TIMER(EncodeOneImage); 88 | 89 | WebPConfig config; 90 | if (!WebPConfigInit(&config)) { 91 | LOG("/!\\ WebPConfigInit() failed."); 92 | return false; 93 | } 94 | SetWebPConfig(&config, write_config); 95 | 96 | WebPPicture pic; 97 | if (!WebPPictureInit(&pic)) { 98 | LOG("/!\\ WebPPictureInit() failed."); 99 | return false; 100 | } 101 | 102 | if (!CastToWebPPicture(config, original_image, &pic)) { 103 | WebPPictureFree(&pic); 104 | return false; 105 | } 106 | 107 | START_TIMER(WebPEncode); 108 | WebPMemoryWriter memory_writer; 109 | WebPMemoryWriterInit(&memory_writer); 110 | pic.writer = WebPMemoryWrite; 111 | pic.custom_ptr = &memory_writer; 112 | if (!WebPEncode(&config, &pic)) { 113 | LOG("/!\\ WebPEncode failed (" << pic.error_code << ")."); 114 | WebPPictureFree(&pic); 115 | return false; 116 | } 117 | STOP_TIMER(WebPEncode); 118 | 119 | WebPPictureFree(&pic); 120 | WebPDataClear(encoded_data); 121 | encoded_data->bytes = memory_writer.mem; 122 | encoded_data->size = memory_writer.size; 123 | // Do not clear memory_writer's data. 124 | LOG("Encoded " << encoded_data->size << " bytes."); 125 | 126 | STOP_TIMER(EncodeOneImage); 127 | return true; 128 | } 129 | 130 | static OSErr GetHostProperty(PIType key, Metadata* const metadata) { 131 | OSErr result = noErr; 132 | 133 | // Code below is inspired from PIGetXMP() and PIGetEXIFData(). 134 | if (sPSProperty->getPropertyProc == nullptr) { 135 | LOG("getPropertyProc is null, considering no " << metadata->four_cc); 136 | } else { 137 | Handle handle = nullptr; 138 | const OSErr error = sPSProperty->getPropertyProc(kPhotoshopSignature, key, 139 | 0, nullptr, &handle); 140 | if (error != noErr) { 141 | LOG("getPropertyProc failed (" << error << "), considering no " 142 | << metadata->four_cc); 143 | } else if (handle == nullptr) { 144 | LOG("handle is null, considering no " << metadata->four_cc); 145 | } else { 146 | const int32 size = sPSHandle->GetSize(handle); 147 | if (size <= 0) { 148 | LOG("size is " << size << ", considering no " << metadata->four_cc); 149 | } else { 150 | Boolean oldLock = FALSE; 151 | Ptr ptr = nullptr; 152 | sPSHandle->SetLock(handle, true, &ptr, &oldLock); 153 | if (ptr == nullptr) { 154 | LOG("/!\\ SetLock failed"); 155 | result = vLckdErr; 156 | } else { 157 | LOG("Got " << size << " bytes of " << metadata->four_cc); 158 | const WebPData view = {(const uint8_t*)ptr, (size_t)size}; 159 | if (!WebPDataCopy(&view, &metadata->chunk)) { 160 | LOG("/!\\ WebPDataCopy of " << metadata->four_cc << " chunk (" 161 | << size << " bytes) failed."); 162 | result = memFullErr; 163 | } 164 | sPSHandle->SetLock(handle, false, &ptr, &oldLock); 165 | } 166 | } 167 | sPSHandle->Dispose(handle); 168 | } 169 | } 170 | return result; 171 | } 172 | 173 | static OSErr GetHostICCProfile(FormatRecordPtr format_record, 174 | Metadata* const metadata) { 175 | OSErr result = noErr; 176 | if (format_record->handleProcs == nullptr) { 177 | LOG("handleProcs is null, considering no ICCP"); 178 | } else if (format_record->handleProcs->lockProc == nullptr) { 179 | LOG("handleProcs->lockProc is null, considering no ICCP"); 180 | } else if (format_record->handleProcs->unlockProc == nullptr) { 181 | LOG("handleProcs->lockProc is null, considering no ICCP"); 182 | } else if (format_record->iCCprofileData == nullptr) { 183 | LOG("iCCprofileData is null, considering no ICCP"); 184 | } else { 185 | Ptr ptr = format_record->handleProcs->lockProc( 186 | format_record->iCCprofileData, false); 187 | if (ptr == nullptr) { 188 | LOG("/!\\ lockProc failed"); 189 | result = vLckdErr; 190 | } else { 191 | const int32_t size = format_record->iCCprofileSize; 192 | if (size <= 0) { 193 | LOG("iCCprofileSize is " << size << ", considering no ICCP"); 194 | } else { 195 | LOG("Got " << size << " bytes of color profile."); 196 | const WebPData view = {(const uint8_t*)ptr, (size_t)size}; 197 | if (!WebPDataCopy(&view, &metadata->chunk)) { 198 | LOG("/!\\ WebPDataCopy of ICCP chunk (" << size << " bytes) failed."); 199 | result = memFullErr; 200 | } 201 | } 202 | format_record->handleProcs->unlockProc(format_record->iCCprofileData); 203 | } 204 | } 205 | return result; 206 | } 207 | 208 | OSErr GetHostMetadata(FormatRecordPtr format_record, 209 | Metadata metadata[Metadata::kNum]) { 210 | DeallocateMetadata(metadata); // Get rid of any previous data. 211 | 212 | OSErr result = GetHostProperty(propEXIFData, &metadata[Metadata::kEXIF]); 213 | if (result != noErr) return result; 214 | 215 | result = GetHostProperty(propXMP, &metadata[Metadata::kXMP]); 216 | if (result != noErr) return result; 217 | 218 | result = GetHostICCProfile(format_record, &metadata[Metadata::kICCP]); 219 | if (result != noErr) return result; 220 | 221 | return noErr; 222 | } 223 | 224 | bool EncodeMetadata(const WriteConfig& write_config, 225 | const Metadata metadata[Metadata::kNum], 226 | WebPData* const encoded_data) { 227 | bool success = true; 228 | WebPMux* mux = nullptr; // Only create mux instance when needed. 229 | const bool keep[Metadata::kNum] = {write_config.keep_exif, 230 | write_config.keep_xmp, 231 | write_config.keep_color_profile}; 232 | 233 | for (int i = 0; i < Metadata::kNum; ++i) { 234 | if (metadata[i].chunk.size > 0 && metadata[i].chunk.bytes != nullptr && 235 | keep[i]) { 236 | if (mux == nullptr) { 237 | // Copy data into mux object as it will be overwritten. 238 | mux = WebPMuxCreate(encoded_data, /*copy_data=*/1); 239 | if (mux == nullptr) { 240 | LOG("/!\\ WebPMuxCreate failed."); 241 | success = false; 242 | break; 243 | } 244 | } 245 | 246 | const WebPMuxError mux_error = WebPMuxSetChunk( 247 | mux, metadata[i].four_cc, &metadata[i].chunk, /*copy_data=*/0); 248 | if (mux_error != WEBP_MUX_OK) { 249 | LOG("/!\\ WebPMuxSetChunk(" << metadata[i].four_cc << ") failed (" 250 | << mux_error << ")."); 251 | success = false; 252 | break; 253 | } else { 254 | LOG("Added " << metadata[i].four_cc << " chunk (" 255 | << metadata[i].chunk.size << " bytes)."); 256 | } 257 | } 258 | } 259 | 260 | if (mux != nullptr) { 261 | if (success) { 262 | WebPData mux_data = {nullptr, 0}; 263 | const WebPMuxError mux_error = WebPMuxAssemble(mux, &mux_data); 264 | if (mux_error != WEBP_MUX_OK) { 265 | LOG("/!\\ WebPMuxAssemble failed (" << mux_error << ")."); 266 | success = false; 267 | WebPDataClear(&mux_data); 268 | } else { 269 | // Now the muxed data can safely replace the previous one. 270 | WebPDataClear(encoded_data); 271 | *encoded_data = mux_data; 272 | mux_data.bytes = nullptr; // Do not free memory. 273 | mux_data.size = 0; 274 | } 275 | } 276 | WebPMuxDelete(mux); 277 | } else { 278 | LOG("No metadata written."); 279 | } 280 | return success; 281 | } 282 | 283 | void WriteToFile(const WebPData& encoded_data, FormatRecordPtr format_record, 284 | int16* const result) { 285 | START_TIMER(WriteToFile); 286 | 287 | if (encoded_data.bytes == nullptr || encoded_data.size == 0) { 288 | LOG("/!\\ Source is null."); 289 | return; 290 | } 291 | 292 | if (*result == noErr) { 293 | // Move cursor to the start of the output file. 294 | *result = PSSDKSetFPos((int32)format_record->dataFork, 295 | format_record->posixFileDescriptor, 296 | format_record->pluginUsingPOSIXIO, fsFromStart, 0); 297 | } 298 | 299 | if (*result == noErr) { 300 | LOG("Writing " << encoded_data.size << " bytes."); 301 | WriteSome(encoded_data.size, encoded_data.bytes, format_record, result); 302 | } 303 | 304 | STOP_TIMER(WriteToFile); 305 | } 306 | -------------------------------------------------------------------------------- /common/WebPShopCanvasUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include "PIFormat.h" 18 | #include "WebPShop.h" 19 | 20 | //------------------------------------------------------------------------------ 21 | 22 | static int CountChannels(const ReadChannelDesc* rgb_channels, 23 | const ReadChannelDesc* a_channels) { 24 | const ReadChannelDesc* channel = rgb_channels; 25 | int channel_index = 0; // To prevent hypothetical infinite loops. 26 | int r = 0, g = 0, b = 0, a = 0; 27 | 28 | while (channel != nullptr && channel_index < MAX_NUM_BROWSED_CHANNELS) { 29 | if (channel->channelType == ctRed) { 30 | r = 1; 31 | } else if (channel->channelType == ctGreen) { 32 | g = 1; 33 | } else if (channel->channelType == ctBlue) { 34 | b = 1; 35 | } 36 | ++channel_index; 37 | channel = channel->next; 38 | } 39 | 40 | channel = a_channels; 41 | channel_index = 0; 42 | 43 | while (channel != nullptr && channel_index < MAX_NUM_BROWSED_CHANNELS) { 44 | if (channel->channelType == ctLayerMask || 45 | channel->channelType == ctTransparency) { 46 | a = 1; 47 | } 48 | ++channel_index; 49 | channel = channel->next; 50 | } 51 | 52 | return r + g + b + a; 53 | } 54 | 55 | //------------------------------------------------------------------------------ 56 | 57 | static void CopyChannel(Data* const data, int16* const result, 58 | const ReadChannelDesc* const channel, 59 | ImageMemoryDesc* const destination) { 60 | if (channel->port == nullptr) { 61 | LOG("/!\\ The channel has no port."); 62 | *result = writErr; 63 | return; 64 | } 65 | 66 | // BGRA order. 67 | switch (channel->channelType) { 68 | case ctRed: { 69 | destination->pixels.bitOffset = channel->depth * 2; 70 | break; 71 | } 72 | case ctGreen: { 73 | destination->pixels.bitOffset = channel->depth * 1; 74 | break; 75 | } 76 | case ctBlue: { 77 | destination->pixels.bitOffset = channel->depth * 0; 78 | break; 79 | } 80 | case ctLayerMask: 81 | case ctTransparency: { 82 | destination->pixels.bitOffset = channel->depth * 3; 83 | break; 84 | } 85 | default: { 86 | LOG("Discarding channel."); 87 | return; 88 | } 89 | } 90 | 91 | Boolean canRead = false; 92 | if (data->sPSChannelPortsSuite == nullptr || 93 | data->sPSChannelPortsSuite->CanRead == nullptr || 94 | data->sPSChannelPortsSuite->CanRead(channel->port, &canRead)) { 95 | LOG("/!\\ Unable to read from channel's port."); 96 | *result = errPlugInHostInsufficient; 97 | return; 98 | } 99 | 100 | VRect read_rect; 101 | read_rect.left = 0; 102 | read_rect.right = destination->width; 103 | read_rect.top = 0; 104 | read_rect.bottom = destination->height; 105 | 106 | if (data->sPSChannelPortsSuite->ReadPixelsFromLevel( 107 | channel->port, 0, &read_rect, &destination->pixels)) { 108 | LOG("/!\\ ReadPixelsFromLevel() failed."); 109 | LOG("Channel " << channel->channelType); 110 | LOG(" depth: " << channel->depth << " / " << destination->pixels.depth); 111 | *result = errPlugInHostInsufficient; 112 | } else if (GetWidth(read_rect) != destination->width || 113 | GetHeight(read_rect) != destination->height) { 114 | LOG("/!\\ Unable to read whole area."); 115 | LOG("Read:"); 116 | LOG(" left " << read_rect.left << ", right " << read_rect.right); 117 | LOG(" top " << read_rect.top << ", bottom " << read_rect.bottom); 118 | LOG("Expected:"); 119 | LOG(" width " << destination->width << ", height " << destination->height); 120 | *result = writErr; 121 | } 122 | } 123 | 124 | static void CopyChannels(Data* const data, int16* const result, 125 | const ReadChannelDesc* rgb_channels, 126 | const ReadChannelDesc* a_channels, int32 canvas_width, 127 | int32 canvas_height, int32 bit_depth, 128 | ImageMemoryDesc* const destination) { 129 | if (rgb_channels == nullptr || destination == nullptr) { 130 | LOG("/!\\ Source or destination is null."); 131 | *result = writErr; 132 | return; 133 | } 134 | 135 | const int num_channels = CountChannels(rgb_channels, a_channels); 136 | if (num_channels < 3 || num_channels > 4) { 137 | LOG("/!\\ Unhandled number of channels: " << num_channels); 138 | *result = writErr; 139 | return; 140 | } 141 | 142 | // The pixels can only be extracted with their original bit depth. 143 | // Four 8-bit channels are needed by WebPEncode() so either allocate them 144 | // directly or just what is necessary and downscale afterwards. 145 | // TODO: Modify CopyChannel() to read line by line to avoid allocating 146 | // the whole canvas twice. 147 | ImageMemoryDesc destination_16b_or_32b; 148 | ImageMemoryDesc* const tmp_dst = 149 | (bit_depth == 8) ? destination : &destination_16b_or_32b; 150 | if (!AllocateImage(tmp_dst, canvas_width, canvas_height, 151 | (bit_depth == 8) ? 4 : num_channels, bit_depth)) { 152 | LOG("/!\\ AllocateImage() failed."); 153 | *result = memFullErr; 154 | return; 155 | } 156 | 157 | const ReadChannelDesc* channel = rgb_channels; 158 | size_t channel_index = 0; 159 | 160 | while (channel != nullptr && channel_index < MAX_NUM_BROWSED_CHANNELS && 161 | *result == noErr) { 162 | CopyChannel(data, result, channel, tmp_dst); 163 | 164 | ++channel_index; 165 | channel = channel->next; 166 | } 167 | 168 | channel = a_channels; 169 | channel_index = 0; 170 | 171 | while (channel != nullptr && channel_index < MAX_NUM_BROWSED_CHANNELS && 172 | *result == noErr) { 173 | CopyChannel(data, result, channel, tmp_dst); 174 | 175 | ++channel_index; 176 | channel = channel->next; 177 | } 178 | 179 | if (num_channels == 3 && tmp_dst->pixels.depth == 8) { 180 | for (size_t y = 0; y < tmp_dst->height; ++y) { 181 | uint8_t* dst_data = reinterpret_cast(tmp_dst->pixels.data) + 182 | y * (tmp_dst->pixels.rowBits / 8) + 183 | /*channel=*/3 * (tmp_dst->pixels.depth / 8); 184 | for (size_t x = 0; x < tmp_dst->width; 185 | ++x, dst_data += tmp_dst->pixels.colBits / 8) { 186 | *dst_data = 255; // Missing opaque alpha channel. 187 | } 188 | } 189 | } 190 | 191 | if (tmp_dst != destination && 192 | !To8bit(destination_16b_or_32b, 193 | /*add_alpha=*/(destination_16b_or_32b.num_channels < 4), 194 | destination)) { 195 | *result = writErr; 196 | } 197 | 198 | DeallocateImage(&destination_16b_or_32b); 199 | } 200 | 201 | //------------------------------------------------------------------------------ 202 | 203 | static bool GetDocumentDimensions(FormatRecordPtr format_record, 204 | int16* const result, 205 | int32* const canvas_width, 206 | int32* const canvas_height, 207 | int32* const bit_depth) { 208 | if (format_record == nullptr || format_record->documentInfo == nullptr || 209 | canvas_width == nullptr || canvas_height == nullptr) { 210 | LOG("/!\\ Unable to access document info or output."); 211 | *result = writErr; 212 | return false; 213 | } 214 | *canvas_width = GetWidth(format_record->documentInfo->bounds); 215 | *canvas_height = GetHeight(format_record->documentInfo->bounds); 216 | *bit_depth = format_record->documentInfo->depth; 217 | if (*canvas_width <= 0 || *canvas_height <= 0) { 218 | LOG("/!\\ Width or height <= 0."); 219 | *result = writErr; 220 | return false; 221 | } 222 | return true; 223 | } 224 | 225 | void CopyWholeCanvas(FormatRecordPtr format_record, Data* const data, 226 | int16* const result, ImageMemoryDesc* const destination) { 227 | START_TIMER(CopyWholeCanvas); 228 | 229 | int32 canvas_width, canvas_height, bit_depth; 230 | if (!GetDocumentDimensions(format_record, result, &canvas_width, 231 | &canvas_height, &bit_depth)) { 232 | return; 233 | } 234 | 235 | CopyChannels(data, result, 236 | format_record->documentInfo->mergedCompositeChannels, 237 | format_record->documentInfo->mergedTransparency, canvas_width, 238 | canvas_height, bit_depth, destination); 239 | LOG("Copied " << destination->num_channels << " channels."); 240 | 241 | STOP_TIMER(CopyWholeCanvas); 242 | } 243 | 244 | void CopyAllLayers(FormatRecordPtr format_record, Data* const data, 245 | int16* const result, 246 | std::vector* const destination) { 247 | START_TIMER(CopyAllLayers); 248 | 249 | if (destination == nullptr) { 250 | LOG("/!\\ Destination is null."); 251 | *result = writErr; 252 | return; 253 | } 254 | 255 | int32 canvas_width, canvas_height, bit_depth; 256 | if (!GetDocumentDimensions(format_record, result, &canvas_width, 257 | &canvas_height, &bit_depth)) { 258 | return; 259 | } 260 | 261 | const size_t expected_num_layers = 262 | (size_t)format_record->documentInfo->layerCount; 263 | if (expected_num_layers < 1 || 264 | expected_num_layers >= MAX_NUM_BROWSED_LAYERS) { 265 | LOG("/!\\ Invalid number of layers (" << expected_num_layers << ")."); 266 | *result = writErr; 267 | return; 268 | } 269 | 270 | ResizeFrameVector(destination, expected_num_layers); 271 | 272 | const ReadLayerDesc* layer_desc = 273 | format_record->documentInfo->layersDescriptor; 274 | size_t layer_count = 0; 275 | while (*result == noErr && layer_desc != nullptr && 276 | layer_count < expected_num_layers) { 277 | int frame_duration; 278 | if (!TryExtractDuration(layer_desc->unicodeName, &frame_duration)) { 279 | LOG("/!\\ Can't extract duration from layer name."); 280 | *result = writErr; 281 | } else { 282 | FrameMemoryDesc& frame = (*destination)[layer_count]; 283 | frame.duration_ms = frame_duration; 284 | 285 | CopyChannels(data, result, layer_desc->compositeChannelsList, 286 | layer_desc->transparency, canvas_width, canvas_height, 287 | bit_depth, &frame.image); 288 | } 289 | layer_desc = layer_desc->next; 290 | ++layer_count; 291 | } 292 | LOG("Copied " << layer_count << " / " << expected_num_layers << " layers."); 293 | if (layer_count != expected_num_layers) { 294 | LOG("/!\\ Missing layers."); 295 | *result = writErr; 296 | } 297 | 298 | STOP_TIMER(CopyAllLayers); 299 | } 300 | 301 | //------------------------------------------------------------------------------ 302 | 303 | void RequestWholeCanvas(FormatRecordPtr format_record, int16* const result) { 304 | format_record->theRect32.top = 0; 305 | format_record->theRect32.bottom = format_record->imageSize32.v; 306 | format_record->theRect32.left = 0; 307 | format_record->theRect32.right = format_record->imageSize32.h; 308 | 309 | const size_t data_size = 310 | (size_t)(format_record->rowBytes * format_record->imageSize32.v); 311 | 312 | LOG("Allocate " << data_size << " bytes."); 313 | Allocate(data_size, &format_record->data, result); 314 | // Image will be copied in format_record->data by Photoshop. 315 | } 316 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /mac/WebPShopUI_mac.mm: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "AppKit/NSAlert.h" 20 | #include "AppKit/NSWindow.h" 21 | #include "PIUI.h" 22 | #include "WebPShop.h" 23 | #include "WebPShopUI.h" 24 | #include "WebPShopUIDialog_mac.h" 25 | #include "WebPShopUIUtils_mac.h" 26 | 27 | //------------------------------------------------------------------------------ 28 | // Adobe Photoshop SDK defines but does not implement these for Mac. 29 | 30 | int PIDialog::Modal(SPPluginRef plugin, const char*, int ID) { return kDNone; } 31 | 32 | bool PICheckBox::GetChecked() { 33 | return (item != nullptr && [(NSButton*)item state] == NSControlStateValueOn); 34 | } 35 | void PICheckBox::SetChecked(bool state) { 36 | if (item != nullptr) { 37 | [(NSButton*)item 38 | setState:(state ? NSControlStateValueOn : NSControlStateValueOff)]; 39 | } 40 | } 41 | void PICheckBox::SetText(const std::string& in) { 42 | if (item != nullptr) { 43 | NSString* nsvalue = 44 | [NSString stringWithCString:in.c_str() 45 | encoding:[NSString defaultCStringEncoding]]; 46 | [(NSButton*)item setTitle:nsvalue]; 47 | } 48 | } 49 | 50 | void PIRadioGroup::SetGroupRange(int32 first, int32 last) { 51 | firstItem = first; 52 | lastItem = last; 53 | } 54 | void PIRadioGroup::SetSelected(int32 s) { 55 | for (int32 item_id = firstItem; item_id <= lastItem; ++item_id) { 56 | NSButton* radio = (NSButton*)((Dialog*)dialog)->GetItemPtr((short)item_id); 57 | if (radio != nullptr) { 58 | [radio setState:((item_id == s) ? NSControlStateValueOn 59 | : NSControlStateValueOff)]; 60 | } 61 | } 62 | } 63 | 64 | void PIText::SetText(const std::string& in) { 65 | if (GetItem() != nullptr) { 66 | NSString* nsvalue = 67 | [NSString stringWithCString:in.c_str() 68 | encoding:[NSString defaultCStringEncoding]]; 69 | [(NSTextField*)GetItem() setStringValue:nsvalue]; 70 | } 71 | } 72 | 73 | //------------------------------------------------------------------------------ 74 | 75 | void PISlider::SetItem(PIDialogPtr dialog, int16 item_id, int min, int max) { 76 | item_ = ((Dialog*)dialog)->GetItemPtr(item_id); 77 | NSSlider* slider = (NSSlider*)item_; 78 | if (slider != nullptr) { 79 | [slider setMinValue:min]; 80 | [slider setMaxValue:max]; 81 | } 82 | } 83 | void PISlider::SetValue(int value) { 84 | NSSlider* slider = (NSSlider*)item_; 85 | if (slider != nullptr) [slider setIntValue:value]; 86 | } 87 | int PISlider::GetValue(void) { 88 | NSSlider* slider = (NSSlider*)item_; 89 | if (slider != nullptr) return [slider intValue]; 90 | return -1; 91 | } 92 | 93 | //------------------------------------------------------------------------------ 94 | 95 | void PIIntegerField::SetItem(PIDialogPtr dialog, int16 item_id, int min, 96 | int max) { 97 | dialog_ = dialog; 98 | item_id_ = item_id; 99 | min_ = std::min(min, max); 100 | max_ = std::max(min, max); 101 | } 102 | void PIIntegerField::SetValue(int value) { 103 | NSTextField* field = (NSTextField*)((Dialog*)dialog_)->GetItemPtr(item_id_); 104 | if (field != nullptr) [field setIntValue:value]; 105 | } 106 | int PIIntegerField::GetValue(void) { 107 | NSTextField* field = (NSTextField*)((Dialog*)dialog_)->GetItemPtr(item_id_); 108 | if (field != nullptr) return [field intValue]; 109 | return -1; 110 | } 111 | 112 | //------------------------------------------------------------------------------ 113 | 114 | PIItem WebPShopDialog::GetItem(short item) { 115 | Dialog* dialog = (Dialog*)GetDialog(); 116 | if (dialog != nullptr) return dialog->GetItemPtr(item); 117 | return nullptr; 118 | } 119 | void WebPShopDialog::ShowItem(short item) { 120 | NSView* view = (NSView*)GetItem(item); 121 | if (view != nullptr) [view setHidden:NO]; 122 | } 123 | void WebPShopDialog::HideItem(short item) { 124 | NSView* view = (NSView*)GetItem(item); 125 | if (view != nullptr) [view setHidden:YES]; 126 | } 127 | void WebPShopDialog::EnableItem(short item) { 128 | NSButton* view = (NSButton*)GetItem(item); 129 | if (view != nullptr) [view setEnabled:YES]; 130 | } 131 | void WebPShopDialog::DisableItem(short item) { 132 | NSButton* view = (NSButton*)GetItem(item); 133 | if (view != nullptr) [view setEnabled:NO]; 134 | } 135 | 136 | //------------------------------------------------------------------------------ 137 | 138 | VRect WebPShopDialog::GetProxyAreaRectInWindow(void) { 139 | WebPShopProxyView* proxy_view = ((Dialog*)GetDialog())->proxy_view; 140 | if (proxy_view != nullptr) { 141 | // Return true number of pixels as displayed on screen (Retina). 142 | NSRect proxy_rect = [proxy_view convertRectToBacking:[proxy_view frame]]; 143 | proxy_rect.size.width -= 2; // Fix to avoid a small clipping on the 144 | proxy_rect.origin.x += 1; // selection's rectangle borders. 145 | proxy_rect.size.height -= 2; // The margin in GetScaleAreaRectInWindow() 146 | proxy_rect.origin.y += 1; // is apparently not enough on Mac. 147 | return {(int32)std::lround(proxy_rect.origin.y), 148 | (int32)std::lround(proxy_rect.origin.x), 149 | (int32)std::lround(proxy_rect.origin.y + proxy_rect.size.height), 150 | (int32)std::lround(proxy_rect.origin.x + proxy_rect.size.width)}; 151 | } 152 | return NullRect(); 153 | } 154 | 155 | void WebPShopDialog::BeginPainting(PaintingContext* const painting_context) { 156 | CGContextRef cg_context = [[NSGraphicsContext currentContext] CGContext]; 157 | painting_context->cg_context = (void*)cg_context; 158 | painting_context->proxy_view = (void*)((Dialog*)GetDialog())->proxy_view; 159 | CGContextSaveGState(cg_context); 160 | } 161 | 162 | void WebPShopDialog::EndPainting(PaintingContext* const painting_context) { 163 | CGContextRef cg_context = (CGContext*)painting_context->cg_context; 164 | CGContextRestoreGState(cg_context); 165 | painting_context->cg_context = nullptr; 166 | painting_context->proxy_view = nullptr; 167 | } 168 | 169 | void WebPShopDialog::ClearRect(const VRect& rect, 170 | PaintingContext* const painting_context) { 171 | // No need to clear anything with Cocoa. 172 | (void)rect; 173 | (void)painting_context; 174 | } 175 | 176 | void WebPShopDialog::DrawRectBorder(uint8_t r, uint8_t g, uint8_t b, 177 | int left, int top, int right, int bottom, 178 | PaintingContext* const painting_context) { 179 | if (painting_context->proxy_view == nullptr) return; 180 | WebPShopProxyView* proxy_view = 181 | (WebPShopProxyView*)painting_context->proxy_view; 182 | 183 | NSRect border_rect = NSMakeRect(left, top, right - left, bottom - top); 184 | border_rect = [proxy_view convertTopLeftRectToCGContext:border_rect]; 185 | 186 | CGContextRef cg_context = (CGContext*)painting_context->cg_context; 187 | CGColorRef color = 188 | CGColorCreateGenericRGB(r / 255.0, g / 255.0, b / 255.0, 1.0); 189 | CGContextSetStrokeColorWithColor(cg_context, color); 190 | CGContextStrokeRectWithWidth(cg_context, border_rect, 1.0); 191 | CGColorRelease(color); 192 | } 193 | 194 | //------------------------------------------------------------------------------ 195 | 196 | bool WebPShopDialog::DisplayImage(const ImageMemoryDesc& image, 197 | const VRect& rect, 198 | DisplayPixelsProc display_pixels_proc, 199 | PaintingContext* const painting_context) { 200 | // The DisplayPixelsProc function (from Photoshop SDK) sometimes crashes or 201 | // displays a corrupted image. Core Graphics functions are used instead. 202 | (void)display_pixels_proc; 203 | 204 | if (image.mode != plugInModeRGBColor) { 205 | LOG("/!\\ Unhandled image mode (" << image.mode << ")."); 206 | return false; 207 | } else if (image.num_channels != 3 && image.num_channels != 4) { 208 | LOG("/!\\ Unhandled channel count (" << image.num_channels << ")."); 209 | return false; 210 | } else if (painting_context->proxy_view == nullptr) { 211 | LOG("/!\\ Missing proxy view."); 212 | return false; 213 | } 214 | CGContextRef cg_context = (CGContext*)painting_context->cg_context; 215 | WebPShopProxyView* proxy_view = 216 | (WebPShopProxyView*)painting_context->proxy_view; 217 | 218 | CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); 219 | CGDataProviderRef data_provider = CGDataProviderCreateWithData( 220 | /*data_to_release=*/nullptr, image.pixels.data, 221 | (image.pixels.rowBits / 8) * image.height, 222 | /*release_function=*/nullptr); 223 | 224 | const CGImageAlphaInfo alpha_info = 225 | (image.num_channels == 4) ? kCGImageAlphaLast : kCGImageAlphaNone; 226 | CGImageRef cg_image = 227 | CGImageCreate(image.width, image.height, image.pixels.depth, 228 | image.pixels.colBits, image.pixels.rowBits / 8, color_space, 229 | kCGBitmapByteOrderDefault | alpha_info, data_provider, 230 | /*color_mapping_function=*/nullptr, 231 | /*should_interpolate=*/false, kCGRenderingIntentDefault); 232 | 233 | NSRect cg_image_rect = NSMakeRect(rect.left, rect.top, rect.right - rect.left, 234 | rect.bottom - rect.top); 235 | cg_image_rect = [proxy_view convertTopLeftRectToCGContext:cg_image_rect]; 236 | 237 | // Display a checkerboard if the image is transparent. 238 | if (image.num_channels == 4) { 239 | const NSSize tile_size = 240 | [proxy_view convertSizeFromBacking:NSMakeSize(8, 8)]; 241 | DrawCheckerboard(cg_context, cg_image_rect, tile_size); 242 | 243 | // Transparency layer to render the image on top of the checkerboard. 244 | // CGContextFlush(cg_context) also works but it's probably slower. 245 | CGContextBeginTransparencyLayerWithRect(cg_context, cg_image_rect, NULL); 246 | } 247 | 248 | CGContextDrawImage(cg_context, cg_image_rect, cg_image); 249 | 250 | if (image.num_channels == 4) CGContextEndTransparencyLayer(cg_context); 251 | CGImageRelease(cg_image); 252 | CGDataProviderRelease(data_provider); 253 | CGColorSpaceRelease(color_space); 254 | return true; 255 | } 256 | 257 | //------------------------------------------------------------------------------ 258 | 259 | void WebPShopDialog::TriggerRepaint() { 260 | WebPShopProxyView* proxy_view = ((Dialog*)GetDialog())->proxy_view; 261 | if (proxy_view != nullptr) [proxy_view display]; 262 | } 263 | 264 | //------------------------------------------------------------------------------ 265 | // Encoding settings UI entry point 266 | 267 | int WebPShopDialog::Modal(SPPluginRef plugin, const char*, int ID) { 268 | SetID(ID); 269 | SetPluginRef(plugin); 270 | 271 | Dialog dialog; 272 | SetDialog((PIDialogPtr)&dialog); 273 | WebPShopDelegate* const delegate = 274 | [[[WebPShopDelegate alloc] init] autorelease]; 275 | [delegate setDialog:this]; 276 | 277 | NSWindow* const window = dialog.CreateWindow(delegate); 278 | [delegate setWindow:window]; 279 | [dialog.proxy_view setDialog:this]; 280 | Init(); 281 | 282 | // This will return only once the window is closed. 283 | [[NSApplication sharedApplication] runModalForWindow:window]; 284 | 285 | // 'delegate' is autoreleased and 'window' is releasedWhenClosed. 286 | SetDialog(nullptr); 287 | return dialog.clicked_button; 288 | } 289 | 290 | //------------------------------------------------------------------------------ 291 | // About box 292 | 293 | void DoAboutBox(SPPluginRef plugin_ref) { 294 | NSAlert* const alert = [[NSAlert alloc] init]; 295 | 296 | [alert addButtonWithTitle:@"OK"]; 297 | [alert addButtonWithTitle:@"developers.google.com/speed/webp"]; 298 | 299 | [alert setMessageText:@"About WebPShop"]; 300 | [alert setInformativeText: 301 | @"WebPShop 0.4.3\nWebP 1.2.2\nA Photoshop plug-in for reading " 302 | @"and writing WebP files.\nCopyright 2019-2022 Google LLC."]; 303 | [alert setAlertStyle:NSAlertStyleInformational]; 304 | 305 | const NSModalResponse buttonPressed = [alert runModal]; 306 | if (buttonPressed == NSAlertSecondButtonReturn) { 307 | sPSFileList->BrowseUrl("https://developers.google.com/speed/webp"); 308 | } 309 | 310 | [alert release]; 311 | } 312 | -------------------------------------------------------------------------------- /common/WebPShop.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // WebPShop is a Photoshop plugin for opening and saving WebP images and 16 | // animations. 17 | // Terms used: 18 | // Host: Photoshop 19 | // Module: plugin 20 | // Format: used with Open, Open As, Save, Save As commands 21 | // Selector: host asking for an action 22 | // UI: related to the window displaying encoding settings 23 | // Proxy: UI preview of the compressed image, to see the quality 24 | // Scale area: if the image is too big to fit in the proxy area, it is 25 | // rescaled to fit in the scale area (left), and a selected 26 | // cropped subimage is displayed (right). 27 | 28 | #ifndef __WebPShop_H__ 29 | #define __WebPShop_H__ 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | #include "PIFormat.h" 36 | #include "PITypes.h" 37 | #include "PIUtilities.h" 38 | #include "WebPShopTerminology.h" 39 | #include "webp/decode.h" 40 | #include "webp/demux.h" 41 | #include "webp/encode.h" 42 | 43 | //------------------------------------------------------------------------------ 44 | // Settings 45 | 46 | // Uncomment to enable logging to a local file. 47 | // #define LOG_TO_FILE "C:/logs/webpshop_log.txt" 48 | 49 | // Uncomment to log the duration of some tasks (requires LOG_TO_FILE). 50 | // #define MEASURE_TIME 51 | 52 | #define MAX_NUM_BROWSED_CHANNELS 16 53 | #define MAX_NUM_BROWSED_LAYERS 4096 54 | 55 | //------------------------------------------------------------------------------ 56 | // Macros 57 | 58 | #ifdef LOG_TO_FILE 59 | #define LOG(STR) \ 60 | do { \ 61 | std::ofstream log(LOG_TO_FILE, std::ios::app); \ 62 | log << STR << std::endl; \ 63 | log.close(); \ 64 | } while (0) 65 | #else 66 | #define LOG(STR) do { } while (0) 67 | #endif // LOG_TO_FILE 68 | 69 | #ifdef MEASURE_TIME 70 | #include 71 | #define START_TIMER(NAME) \ 72 | std::chrono::steady_clock::time_point begin_##NAME = \ 73 | std::chrono::steady_clock::now() 74 | #define STOP_TIMER(NAME) \ 75 | LOG(#NAME " took " \ 76 | << (std::chrono::duration_cast( \ 77 | std::chrono::steady_clock::now() - begin_##NAME) \ 78 | .count() / \ 79 | 1000000.0) \ 80 | << " seconds.") 81 | #else 82 | #define START_TIMER(NAME) do { } while (0) 83 | #define STOP_TIMER(NAME) do { } while (0) 84 | #endif // MEASURE_TIME 85 | 86 | //------------------------------------------------------------------------------ 87 | // Data 88 | 89 | enum Compression { FASTEST = 0, DEFAULT = 1, SLOWEST = 2 }; 90 | 91 | // Encoding parameters, closely tied to the UI. 92 | struct WriteConfig { 93 | int quality; // [0..100] 94 | Compression compression; 95 | bool keep_exif; 96 | bool keep_xmp; 97 | bool keep_color_profile; 98 | bool loop_forever; 99 | bool animation; 100 | bool display_proxy; 101 | }; 102 | 103 | struct Metadata { 104 | enum kType { kEXIF, kXMP, kICCP, kNum }; 105 | const char* four_cc; 106 | WebPData chunk; 107 | }; 108 | 109 | // An instance of Data will be allocated on the first time the plugin is 110 | // solicited and it will be freed by Photoshop. Everything that must stay 111 | // between plugin calls should go into it (to avoid globals). 112 | struct Data { 113 | WebPDecoderConfig read_config; 114 | WriteConfig write_config; 115 | size_t file_size; 116 | void* file_data; 117 | Metadata metadata[Metadata::kNum]; 118 | WebPData encoded_data; 119 | WebPAnimDecoder* anim_decoder; 120 | int last_frame_timestamp; 121 | uint16 layer_name_buffer[256]; 122 | PSChannelPortsSuite1* sPSChannelPortsSuite; 123 | }; 124 | 125 | // Stores an image. 126 | struct ImageMemoryDesc { 127 | PixelMemoryDesc pixels = {nullptr, 0, 0, 0, 0}; 128 | int32 width = 0; 129 | int32 height = 0; 130 | int num_channels = 0; 131 | int32 mode = 0; 132 | }; 133 | 134 | // Stores a frame (image with a duration). 135 | struct FrameMemoryDesc { 136 | ImageMemoryDesc image; 137 | int duration_ms = 0; 138 | }; 139 | 140 | //------------------------------------------------------------------------------ 141 | // User interface 142 | 143 | // Displays a window with writing (encoding) options. 144 | bool DoUI(WriteConfig* const write_config, 145 | const Metadata metadata[Metadata::kNum], SPPluginRef plugin_ref, 146 | const std::vector& original_frames, 147 | bool original_frames_were_converted_to_8b, 148 | WebPData* const encoded_data, DisplayPixelsProc display_pixels_proc); 149 | void DoAboutBox(SPPluginRef plugin_ref); 150 | 151 | // Loads and saves scripting parameters. 152 | void LoadWriteConfig(FormatRecordPtr format_record, 153 | WriteConfig* const write_config, int16* const result); 154 | void SaveWriteConfig(FormatRecordPtr format_record, 155 | const WriteConfig& write_config, int16* const result); 156 | 157 | void LoadPOSIXConfig(FormatRecordPtr format_record, int16* const result); 158 | 159 | //------------------------------------------------------------------------------ 160 | // Utils 161 | 162 | // This routine adds a history entry with the current system date and time 163 | // to the file opened by host. 164 | void AddComment(FormatRecordPtr format_record, Data* const data, 165 | int16* const result); 166 | 167 | //------------------------------------------------------------------------------ 168 | // Data utils 169 | 170 | // Reads from and writes to file opened by host. 171 | void ReadSome(size_t count, void* const buffer, FormatRecordPtr format_record, 172 | int16* const result); 173 | void WriteSome(size_t count, const void* const buffer, 174 | FormatRecordPtr format_record, int16* const result); 175 | 176 | // Checks that first bytes are "RIFF" then file size then "WEBP". 177 | bool ReadAndCheckHeader(FormatRecordPtr format_record, int16* const result, 178 | size_t* file_size); 179 | 180 | // Memory managed by host. 181 | void Allocate(size_t count, void** const buffer, int16* const result); 182 | void AllocateAndRead(size_t count, void** const buffer, 183 | FormatRecordPtr format_record, int16* const result); 184 | void Deallocate(void** const buffer); 185 | 186 | //------------------------------------------------------------------------------ 187 | // Image utils 188 | 189 | bool AllocateImage(ImageMemoryDesc* const image, int32 width, int32 height, 190 | int num_channels, int32 bit_depth); 191 | void DeallocateImage(ImageMemoryDesc* const image); 192 | void DeallocateMetadata(Metadata metadata[Metadata::kNum]); 193 | 194 | // Deallocates frames that won't be used after vector::resize(). 195 | void ResizeFrameVector(std::vector* const frames, size_t size); 196 | void ClearFrameVector(std::vector* const frames); 197 | 198 | bool Scale(const ImageMemoryDesc& src, ImageMemoryDesc* const dst, 199 | size_t dst_width, size_t dst_height); 200 | bool Crop(const ImageMemoryDesc& src, ImageMemoryDesc* const dst, 201 | size_t crop_width, size_t crop_height, size_t crop_left, 202 | size_t crop_top); 203 | bool To8bit(const ImageMemoryDesc& src, bool add_alpha, 204 | ImageMemoryDesc* const dst); 205 | 206 | //------------------------------------------------------------------------------ 207 | // Dimensions utils 208 | 209 | int32 GetWidth(const VRect& rect); 210 | int32 GetHeight(const VRect& rect); 211 | bool ScaleToFit(int32* const width, int32* const height, int32 max_width, 212 | int32 max_height); 213 | bool CropToFit(int32* const width, int32* const height, int32 left, int32 top, 214 | int32 max_width, int32 max_height); 215 | VRect ScaleRectFromAreaToArea(const VRect& src, int32 src_area_width, 216 | int32 src_area_height, int32 dst_area_width, 217 | int32 dst_area_height); 218 | 219 | //------------------------------------------------------------------------------ 220 | // Canvas utils 221 | 222 | // Requests data for whole canvas from host, stores it in format_record->data. 223 | void RequestWholeCanvas(FormatRecordPtr format_record, int16* const result); 224 | // Copies merged canvas from host into destination. 225 | void CopyWholeCanvas(FormatRecordPtr format_record, Data* const data, 226 | int16* const result, ImageMemoryDesc* const destination); 227 | // Copies each layer (without effects) from host into destination. 228 | void CopyAllLayers(FormatRecordPtr format_record, Data* const data, 229 | int16* const result, 230 | std::vector* const destination); 231 | 232 | //------------------------------------------------------------------------------ 233 | // Encode utils 234 | 235 | // Fills config parameters with settings in write_config. 236 | void SetWebPConfig(WebPConfig* const config, const WriteConfig& write_config); 237 | 238 | // Wraps an ImageMemoryDesc into a WebPPicture. 239 | // WebPPictureInit() must be called on 'dst' prior to calling this and 240 | // WebPPictureFree() must be called afterwards. 241 | bool CastToWebPPicture(const WebPConfig& config, const ImageMemoryDesc& src, 242 | WebPPicture* const dst); 243 | 244 | // Encodes original_image into encoded_data. 245 | bool EncodeOneImage(const ImageMemoryDesc& original_image, 246 | const WriteConfig& write_config, 247 | WebPData* const encoded_data); 248 | bool EncodeAllFrames(const std::vector& original_frames, 249 | const WriteConfig& write_config, 250 | WebPData* const encoded_data); 251 | 252 | // Retrieves metadata from host (current Photoshop document). 253 | OSErr GetHostMetadata(FormatRecordPtr format_record, 254 | Metadata metadata[Metadata::kNum]); 255 | // Adds kept available metadata (EXIF, XMP, ICC) to encoded_data. 256 | bool EncodeMetadata(const WriteConfig& write_config, 257 | const Metadata metadata[Metadata::kNum], 258 | WebPData* const encoded_data); 259 | 260 | // Writes encoded_data to file opened by host. 261 | void WriteToFile(const WebPData& encoded_data, FormatRecordPtr format_record, 262 | int16* const result); 263 | 264 | //------------------------------------------------------------------------------ 265 | // Decode utils 266 | 267 | // Decodes encoded_data into compressed_image. 268 | bool DecodeOneImage(const WebPData& encoded_data, 269 | ImageMemoryDesc* const compressed_image); 270 | bool DecodeAllFrames(const WebPData& encoded_data, 271 | std::vector* const compressed_frames); 272 | 273 | // Gets available metadata from encoded_data. 274 | bool DecodeMetadata(const WebPData& encoded_data, 275 | Metadata metadata[Metadata::kNum]); 276 | // Sends metadata to host (current Photoshop document). 277 | OSErr SetHostMetadata(FormatRecordPtr format_record, 278 | const Metadata metadata[Metadata::kNum]); 279 | 280 | // Decodes file opened by host into format_record->data. 281 | void InitAnimDecoder(FormatRecordPtr format_record, Data* const data, 282 | int16* const result); 283 | void ReadOneFrame(FormatRecordPtr format_record, Data* const data, 284 | int16* const result, int frame_counter); 285 | void ReleaseAnimDecoder(FormatRecordPtr format_record, Data* const data); 286 | 287 | // Sets FormatRecord members that should be defined in both the start and 288 | // continue selectors, according to PIFormat.h. 289 | void SetPlaneColRowBytes(FormatRecordPtr format_record); 290 | 291 | //------------------------------------------------------------------------------ 292 | // Animation utils 293 | 294 | // Prints "Frame [frame_index + 1] ([frame_duration] ms)" to output. 295 | void PrintDuration(int frame_index, int frame_duration, uint16 output[], 296 | size_t output_max_length); 297 | // Extracts a number from last encountered frame duration syntax "(123 ms)". 298 | bool TryExtractDuration(const uint16* const layer_name, int* const duration_ms); 299 | 300 | //------------------------------------------------------------------------------ 301 | 302 | #endif // __WebPShop_H__ 303 | -------------------------------------------------------------------------------- /mac/WebPShopUIDialog_mac.mm: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "WebPShopUIDialog_mac.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include "AppKit/NSWindow.h" 21 | 22 | //------------------------------------------------------------------------------ 23 | 24 | @implementation WebPShopProxyView 25 | - (id)initWithFrame:(NSRect)frame { 26 | self = [super initWithFrame:frame]; 27 | return self; 28 | } 29 | - (void)drawRect:(NSRect)rect { 30 | if (self.dialog != nullptr) self.dialog->PaintProxy(); 31 | [super drawRect:rect]; 32 | } 33 | - (void)mouseDragged:(NSEvent *)event { 34 | NSPoint cursor_position = [event locationInWindow]; 35 | // Cocoa uses lower-left-based coordinates. 36 | cursor_position = [self convertPoint:cursor_position fromView:nil]; 37 | cursor_position.y = [self frame].size.height - cursor_position.y - 1; 38 | cursor_position = [self convertPoint:cursor_position toView:nil]; 39 | // Return true number of pixels as displayed on screen (Retina). 40 | cursor_position = [self convertPointToBacking:cursor_position]; 41 | self.dialog->OnMouseMove((int)std::lround(cursor_position.x), 42 | (int)std::lround(cursor_position.y), 43 | /*left_button_is_held_down=*/true); 44 | } 45 | - (void)mouseDown:(NSEvent *)event { 46 | [self mouseDragged:event]; 47 | } 48 | - (NSRect)convertTopLeftRectToCGContext:(NSRect)rect { 49 | const NSRect proxy_rect = [self convertRectToBacking:[self frame]]; 50 | // Core Graphics functions draw in the interior coordinate system of the 51 | // current view, not in the whole window coordinate system. 52 | rect.origin.x -= proxy_rect.origin.x; 53 | rect.origin.y -= proxy_rect.origin.y; 54 | // Cocoa is bottom-left based. 55 | rect.origin.y = proxy_rect.size.height - rect.origin.y - rect.size.height; 56 | // Core Graphics functions take local space coordinates, not the true number 57 | // of pixels displayed on screen. 58 | return [self convertRectFromBacking:rect]; 59 | } 60 | @end 61 | 62 | //------------------------------------------------------------------------------ 63 | 64 | @implementation WebPShopDelegate 65 | - (id)init { 66 | self = [super init]; 67 | return self; 68 | } 69 | - (void)clickOk:(id)sender { 70 | ((Dialog*)self.dialog->GetDialog())->clicked_button = kDOK; 71 | [self.window performClose:sender]; 72 | } 73 | - (void)clickCancel:(id)sender { 74 | ((Dialog*)self.dialog->GetDialog())->clicked_button = kDCancel; 75 | [self.window performClose:sender]; 76 | } 77 | - (void)controlTextDidChange:(NSNotification *)obj { 78 | [self notified:[obj object]]; 79 | } 80 | - (void)notified:(NSObject *)sender { 81 | self.dialog->Notify( 82 | ((Dialog*)self.dialog->GetDialog())->GetItemId((PIItem)sender)); 83 | } 84 | - (void)windowWillClose:(NSNotification *)notification { 85 | [NSApp stopModal]; 86 | } 87 | @end 88 | 89 | //------------------------------------------------------------------------------ 90 | 91 | void Dialog::Set(short item_id, void* const item_ptr) { 92 | if (item_id == kDNone) return; 93 | item_id_to_ptr.insert(std::make_pair(item_id, (PIItem)item_ptr)); 94 | } 95 | PIItem Dialog::GetItemPtr(short item_id) { 96 | const auto item = item_id_to_ptr.find(item_id); 97 | if (item != item_id_to_ptr.end()) return item->second; 98 | return nullptr; 99 | } 100 | short Dialog::GetItemId(PIItem item_ptr) { 101 | for (const auto& item : item_id_to_ptr) { 102 | if (item.second == item_ptr) return item.first; 103 | } 104 | return kDNone; 105 | } 106 | 107 | //------------------------------------------------------------------------------ 108 | 109 | NSWindow* Dialog::CreateWindow(WebPShopDelegate* const delegate) { 110 | LOG("UI dialog window creation"); 111 | NSWindow* window = [[NSWindow alloc] 112 | initWithContentRect:NSMakeRect(0, 0, 669, 538) 113 | styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable 114 | backing:NSBackingStoreBuffered 115 | defer:NO]; 116 | [window setTitle:@"WebPShop"]; 117 | [window setReleasedWhenClosed:YES]; 118 | [window setDelegate:delegate]; 119 | 120 | LOG(" General elements"); 121 | Set(kDWebPText, 122 | settings_text = [NSTextField labelWithString:@"WebP settings"]); 123 | [[window contentView] addSubview:settings_text]; 124 | [settings_text setFrame:NSMakeRect(6, 517, 200, 16)]; 125 | [settings_text setFont:[NSFont systemFontOfSize:11]]; 126 | 127 | Set(kDOK, ok_button = [NSButton buttonWithTitle:@"OK" 128 | target:delegate 129 | action:@selector(clickOk:)]); 130 | [[window contentView] addSubview:ok_button]; 131 | [ok_button setFrame:NSMakeRect(600, 508, 70, 25)]; 132 | 133 | Set(kDCancel, 134 | cancel_button = [NSButton buttonWithTitle:@"Cancel" 135 | target:delegate 136 | action:@selector(clickCancel:)]); 137 | [[window contentView] addSubview:cancel_button]; 138 | [cancel_button setFrame:NSMakeRect(600, 478, 70, 25)]; 139 | 140 | LOG(" Quality elements"); 141 | Set(kDNone, 142 | quality_box = [[NSBox alloc] initWithFrame:NSMakeRect(5, 448, 204, 70)]); 143 | [[window contentView] addSubview:quality_box]; 144 | [quality_box setTitle:@"Quality"]; 145 | 146 | Set(kDQualitySlider, 147 | quality_slider = 148 | [NSSlider sliderWithTarget:delegate action:@selector(notified:)]); 149 | [[window contentView] addSubview:quality_slider]; 150 | [quality_slider setFrame:NSMakeRect(40, 478, 100, 20)]; 151 | 152 | Set(kDQualityField, quality_field = [NSTextField textFieldWithString:@"75"]); 153 | [[window contentView] addSubview:quality_field]; 154 | [quality_field setFrame:NSMakeRect(170, 478, 30, 22)]; 155 | [quality_field setDelegate:delegate]; 156 | 157 | Set(kDNone, quality_text_smallest = 158 | [NSTextField labelWithString:@"Lossy"]); 159 | [[window contentView] addSubview:quality_text_smallest]; 160 | [quality_text_smallest setFrame:NSMakeRect(20, 458, 80, 16)]; 161 | [quality_text_smallest setFont:[NSFont systemFontOfSize:11]]; 162 | 163 | Set(kDNone, 164 | quality_text_lossless = [NSTextField labelWithString:@"Lossless"]); 165 | [[window contentView] addSubview:quality_text_lossless]; 166 | [quality_text_lossless setFrame:NSMakeRect(120, 458, 80, 16)]; 167 | [quality_text_lossless setFont:[NSFont systemFontOfSize:11]]; 168 | 169 | LOG(" Compression elements"); 170 | Set(kDNone, compression_box = 171 | [[NSBox alloc] initWithFrame:NSMakeRect(210, 448, 95, 80)]); 172 | [[window contentView] addSubview:compression_box]; 173 | [compression_box setTitle:@"Compression"]; 174 | 175 | Set(kDCompressionFastest, compression_radio_button_fastest = [NSButton 176 | radioButtonWithTitle:@"Fastest" 177 | target:delegate 178 | action:@selector(notified:)]); 179 | [[window contentView] addSubview:compression_radio_button_fastest]; 180 | [compression_radio_button_fastest setFrame:NSMakeRect(220, 493, 66, 20)]; 181 | [compression_radio_button_fastest setFont:[NSFont systemFontOfSize:11]]; 182 | 183 | Set(kDCompressionDefault, compression_radio_button_default = [NSButton 184 | radioButtonWithTitle:@"Default" 185 | target:delegate 186 | action:@selector(notified:)]); 187 | [[window contentView] addSubview:compression_radio_button_default]; 188 | [compression_radio_button_default setFrame:NSMakeRect(220, 473, 66, 20)]; 189 | [compression_radio_button_default setFont:[NSFont systemFontOfSize:11]]; 190 | 191 | Set(kDCompressionSlowest, compression_radio_button_smallest = [NSButton 192 | radioButtonWithTitle:@"Slowest" 193 | target:delegate 194 | action:@selector(notified:)]); 195 | [[window contentView] addSubview:compression_radio_button_smallest]; 196 | [compression_radio_button_smallest setFrame:NSMakeRect(220, 453, 66, 20)]; 197 | [compression_radio_button_smallest setFont:[NSFont systemFontOfSize:11]]; 198 | 199 | LOG(" Metadata elements"); 200 | Set(kDNone, metadata_box = 201 | [[NSBox alloc] initWithFrame:NSMakeRect(306, 448, 195, 80)]); 202 | [[window contentView] addSubview:metadata_box]; 203 | [metadata_box setTitle:@"Metadata"]; 204 | 205 | Set(kDKeepExif, metadata_exif_checkbox = 206 | [NSButton checkboxWithTitle:@"Keep EXIF" 207 | target:delegate 208 | action:@selector(notified:)]); 209 | [[window contentView] addSubview:metadata_exif_checkbox]; 210 | [metadata_exif_checkbox setFrame:NSMakeRect(316, 493, 80, 20)]; 211 | [metadata_exif_checkbox setFont:[NSFont systemFontOfSize:11]]; 212 | 213 | Set(kDKeepXmp, metadata_xmp_checkbox = 214 | [NSButton checkboxWithTitle:@"Keep XMP" 215 | target:delegate 216 | action:@selector(notified:)]); 217 | [[window contentView] addSubview:metadata_xmp_checkbox]; 218 | [metadata_xmp_checkbox setFrame:NSMakeRect(316, 473, 80, 20)]; 219 | [metadata_xmp_checkbox setFont:[NSFont systemFontOfSize:11]]; 220 | 221 | Set(kDKeepColorProfile, metadata_iccp_checkbox = 222 | [NSButton checkboxWithTitle:@"Keep ICC" 223 | target:delegate 224 | action:@selector(notified:)]); 225 | [[window contentView] addSubview:metadata_iccp_checkbox]; 226 | [metadata_iccp_checkbox setFrame:NSMakeRect(316, 453, 80, 20)]; 227 | [metadata_iccp_checkbox setFont:[NSFont systemFontOfSize:11]]; 228 | 229 | Set(kDLoopForever, metadata_loop_checkbox = 230 | [NSButton checkboxWithTitle:@"Loop" 231 | target:delegate 232 | action:@selector(notified:)]); 233 | [[window contentView] addSubview:metadata_loop_checkbox]; 234 | [metadata_loop_checkbox setFrame:NSMakeRect(400, 493, 80, 20)]; 235 | [metadata_loop_checkbox setFont:[NSFont systemFontOfSize:11]]; 236 | 237 | LOG(" Proxy"); 238 | Set(kDNone, 239 | proxy_box = [[NSBox alloc] initWithFrame:NSMakeRect(5, 5, 659, 433)]); 240 | [[window contentView] addSubview:proxy_box]; 241 | [proxy_box setTitle:@""]; // The checkbox replaces the title. 242 | 243 | Set(kDProxyCheckbox, 244 | proxy_checkbox = [NSButton checkboxWithTitle:@"Preview" 245 | target:delegate 246 | action:@selector(notified:)]); 247 | [[window contentView] addSubview:proxy_checkbox]; 248 | [proxy_checkbox setFrame:NSMakeRect(20, 423, 160, 22)]; 249 | [proxy_checkbox setFont:[NSFont systemFontOfSize:11]]; 250 | 251 | const CGFloat proxy_padding = 10; 252 | NSRect proxy_view_rect = 253 | NSMakeRect([proxy_box frame].origin.x + proxy_padding, 254 | [proxy_box frame].origin.y + proxy_padding, 255 | [proxy_box frame].size.width - 2 * proxy_padding, 256 | [proxy_box frame].size.height - 2 * proxy_padding - 10); 257 | Set(kDNone, 258 | proxy_view = [[WebPShopProxyView alloc] initWithFrame:proxy_view_rect]); 259 | [[window contentView] addSubview:proxy_view]; 260 | 261 | LOG(" Animation"); 262 | Set(kDFrameText, frame_text = [NSTextField labelWithString:@"Frame:"]); 263 | [[window contentView] addSubview:frame_text]; 264 | [frame_text setFrame:NSMakeRect(485, 422, 40, 22)]; 265 | [frame_text setFont:[NSFont systemFontOfSize:11]]; 266 | 267 | Set(kDFrameSlider, 268 | frame_index_slider = 269 | [NSSlider sliderWithTarget:delegate action:@selector(notified:)]); 270 | [[window contentView] addSubview:frame_index_slider]; 271 | [frame_index_slider setFrame:NSMakeRect(525, 426, 50, 22)]; 272 | 273 | Set(kDFrameField, frame_index_field = [NSTextField textFieldWithString:@"1"]); 274 | [[window contentView] addSubview:frame_index_field]; 275 | [frame_index_field setFrame:NSMakeRect(580, 426, 30, 22)]; 276 | [frame_index_field setDelegate:delegate]; 277 | 278 | Set(kDFrameDurationText, 279 | frame_duration_text = [NSTextField labelWithString:@"100 ms"]); 280 | [[window contentView] addSubview:frame_duration_text]; 281 | [frame_duration_text setFrame:NSMakeRect(515, 422, 40, 22)]; 282 | [frame_duration_text setFont:[NSFont systemFontOfSize:11]]; 283 | [frame_duration_text setAlignment:NSTextAlignmentCenter]; 284 | 285 | return window; 286 | } 287 | 288 | //------------------------------------------------------------------------------ 289 | --------------------------------------------------------------------------------