├── .mvn ├── maven.config └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── misc ├── icon.ico ├── splash.bmp ├── icon_terminal.ico └── icon_terminal.png ├── src ├── main │ ├── resources │ │ ├── img │ │ │ ├── eye.png │ │ │ ├── web.png │ │ │ ├── android.png │ │ │ ├── iphone.png │ │ │ ├── windows.png │ │ │ ├── file-symbol.png │ │ │ ├── screenshot1.jpg │ │ │ ├── folder-symbol.png │ │ │ ├── ldpi_comparison.png │ │ │ ├── mdpi_comparison.png │ │ │ ├── comparison_icons_48.png │ │ │ ├── comparison_icons_72.png │ │ │ ├── comparison_photo_144.png │ │ │ ├── comparison_photo_72.png │ │ │ ├── ninepatch_account.9.png │ │ │ ├── ninepatch_bubble.9.png │ │ │ ├── density_converter_icon.psd │ │ │ ├── density_converter_icon_16.png │ │ │ ├── density_converter_icon_24.png │ │ │ ├── density_converter_icon_48.png │ │ │ ├── density_converter_icon_64.png │ │ │ ├── density_converter_icon_128.png │ │ │ └── density_converter_icon_256.png │ │ └── style.css │ └── java │ │ └── at │ │ └── favre │ │ └── tools │ │ └── dconvert │ │ ├── arg │ │ ├── EScaleMode.java │ │ ├── RoundingHandler.java │ │ ├── EOutputCompressionMode.java │ │ ├── EPlatform.java │ │ ├── ImageType.java │ │ └── EScalingAlgorithm.java │ │ ├── converters │ │ ├── scaling │ │ │ ├── ScaleAlgorithm.java │ │ │ ├── NaiveGraphics2dAlgorithm.java │ │ │ ├── ResampleAlgorithm.java │ │ │ ├── ProgressiveAlgorithm.java │ │ │ ├── ThumbnailnatorProgressiveAlgorithm.java │ │ │ └── ImageHandler.java │ │ ├── ConverterCallback.java │ │ ├── descriptors │ │ │ ├── PostfixDescriptor.java │ │ │ ├── DensityDescriptor.java │ │ │ └── AndroidDensityDescriptor.java │ │ ├── Result.java │ │ ├── IPlatformConverter.java │ │ ├── postprocessing │ │ │ ├── IPostProcessor.java │ │ │ ├── MozJpegProcessor.java │ │ │ ├── APostProcessor.java │ │ │ ├── PngCrushProcessor.java │ │ │ └── WebpProcessor.java │ │ ├── WebConverter.java │ │ ├── WindowsConverter.java │ │ ├── AndroidConverter.java │ │ ├── APlatformConverter.java │ │ └── IOSConverter.java │ │ ├── exceptions │ │ ├── InvalidArgumentException.java │ │ └── NinePatchException.java │ │ ├── ui │ │ ├── IPreferenceStore.java │ │ ├── SerializePreferenceStore.java │ │ ├── GUI.java │ │ └── TaskbarProgress.java │ │ ├── util │ │ ├── LoadedImage.java │ │ ├── MiscUtil.java │ │ ├── DensityBucketUtil.java │ │ ├── PostProcessorUtil.java │ │ ├── ImageUtil.java │ │ └── NinePatchScaler.java │ │ ├── Main.java │ │ └── WorkerHandler.java └── test │ ├── resources │ ├── bmp_example_256.bmp │ ├── gif_example_640.gif │ ├── jpg_example2_512.jpg │ ├── jpg_example_1920.jpg │ ├── png_example4_500.png │ ├── psd_example_827.psd │ ├── tiff_example_256.tif │ ├── ninepatch_bubble.9.png │ ├── png_example1_alpha_144.png │ ├── png_example2_alpha_144.png │ ├── png_example3_alpha_128.png │ └── svg_example_512.svg │ └── java │ └── at │ └── favre │ └── tools │ └── dconvert │ └── test │ ├── helper │ ├── TestPreferenceStore.java │ ├── MockException.java │ └── MockProcessor.java │ ├── AIntegrationTest.java │ ├── WebConverterTest.java │ ├── WindowsConverterTest.java │ ├── CLISystemTest.java │ ├── DConvertTest.java │ ├── IOSConverterTest.java │ ├── AndroidConverterTest.java │ └── WorkerHandlerTest.java ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── build_deploy.yml ├── checkstyle-suppressions.xml ├── .travis.yml ├── CHANGELOG └── mvnw.cmd /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -DcommonConfig.compiler.profile=jdk11_w_errorprone 2 | -------------------------------------------------------------------------------- /misc/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/misc/icon.ico -------------------------------------------------------------------------------- /misc/splash.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/misc/splash.bmp -------------------------------------------------------------------------------- /misc/icon_terminal.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/misc/icon_terminal.ico -------------------------------------------------------------------------------- /misc/icon_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/misc/icon_terminal.png -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/img/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/eye.png -------------------------------------------------------------------------------- /src/main/resources/img/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/web.png -------------------------------------------------------------------------------- /src/main/resources/img/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/android.png -------------------------------------------------------------------------------- /src/main/resources/img/iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/iphone.png -------------------------------------------------------------------------------- /src/main/resources/img/windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/windows.png -------------------------------------------------------------------------------- /src/main/resources/img/file-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/file-symbol.png -------------------------------------------------------------------------------- /src/main/resources/img/screenshot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/screenshot1.jpg -------------------------------------------------------------------------------- /src/test/resources/bmp_example_256.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/test/resources/bmp_example_256.bmp -------------------------------------------------------------------------------- /src/test/resources/gif_example_640.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/test/resources/gif_example_640.gif -------------------------------------------------------------------------------- /src/test/resources/jpg_example2_512.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/test/resources/jpg_example2_512.jpg -------------------------------------------------------------------------------- /src/test/resources/jpg_example_1920.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/test/resources/jpg_example_1920.jpg -------------------------------------------------------------------------------- /src/test/resources/png_example4_500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/test/resources/png_example4_500.png -------------------------------------------------------------------------------- /src/test/resources/psd_example_827.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/test/resources/psd_example_827.psd -------------------------------------------------------------------------------- /src/test/resources/tiff_example_256.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/test/resources/tiff_example_256.tif -------------------------------------------------------------------------------- /src/main/resources/img/folder-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/folder-symbol.png -------------------------------------------------------------------------------- /src/main/resources/img/ldpi_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/ldpi_comparison.png -------------------------------------------------------------------------------- /src/main/resources/img/mdpi_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/mdpi_comparison.png -------------------------------------------------------------------------------- /src/test/resources/ninepatch_bubble.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/test/resources/ninepatch_bubble.9.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea 4 | .DS_Store 5 | /build 6 | /captures 7 | *.iml 8 | /target 9 | pom.xml.versionsBackup 10 | -------------------------------------------------------------------------------- /src/main/resources/img/comparison_icons_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/comparison_icons_48.png -------------------------------------------------------------------------------- /src/main/resources/img/comparison_icons_72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/comparison_icons_72.png -------------------------------------------------------------------------------- /src/main/resources/img/comparison_photo_144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/comparison_photo_144.png -------------------------------------------------------------------------------- /src/main/resources/img/comparison_photo_72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/comparison_photo_72.png -------------------------------------------------------------------------------- /src/main/resources/img/ninepatch_account.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/ninepatch_account.9.png -------------------------------------------------------------------------------- /src/main/resources/img/ninepatch_bubble.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/ninepatch_bubble.9.png -------------------------------------------------------------------------------- /src/test/resources/png_example1_alpha_144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/test/resources/png_example1_alpha_144.png -------------------------------------------------------------------------------- /src/test/resources/png_example2_alpha_144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/test/resources/png_example2_alpha_144.png -------------------------------------------------------------------------------- /src/test/resources/png_example3_alpha_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/test/resources/png_example3_alpha_128.png -------------------------------------------------------------------------------- /src/main/resources/img/density_converter_icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/density_converter_icon.psd -------------------------------------------------------------------------------- /src/main/resources/img/density_converter_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/density_converter_icon_16.png -------------------------------------------------------------------------------- /src/main/resources/img/density_converter_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/density_converter_icon_24.png -------------------------------------------------------------------------------- /src/main/resources/img/density_converter_icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/density_converter_icon_48.png -------------------------------------------------------------------------------- /src/main/resources/img/density_converter_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/density_converter_icon_64.png -------------------------------------------------------------------------------- /src/main/resources/img/density_converter_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/density_converter_icon_128.png -------------------------------------------------------------------------------- /src/main/resources/img/density_converter_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickfav/density-converter/HEAD/src/main/resources/img/density_converter_icon_256.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # dependabot analyzing maven dependencies 2 | version: 2 3 | updates: 4 | - package-ecosystem: "maven" 5 | directory: "/" 6 | open-pull-requests-limit: 3 7 | schedule: 8 | interval: "weekly" 9 | labels: 10 | - "dependencies" 11 | -------------------------------------------------------------------------------- /src/test/java/at/favre/tools/dconvert/test/helper/TestPreferenceStore.java: -------------------------------------------------------------------------------- 1 | package at.favre.tools.dconvert.test.helper; 2 | 3 | import at.favre.tools.dconvert.arg.Arguments; 4 | import at.favre.tools.dconvert.ui.IPreferenceStore; 5 | import org.junit.Ignore; 6 | 7 | /** 8 | * For UI tests 9 | */ 10 | @Ignore("not a test class") 11 | public class TestPreferenceStore implements IPreferenceStore { 12 | private Arguments arg; 13 | 14 | @Override 15 | public void save(Arguments arg) { 16 | this.arg = arg; 17 | } 18 | 19 | @Override 20 | public Arguments get() { 21 | return arg; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/at/favre/tools/dconvert/test/helper/MockException.java: -------------------------------------------------------------------------------- 1 | package at.favre.tools.dconvert.test.helper; 2 | 3 | /** 4 | * Exception to throw in test 5 | */ 6 | public class MockException extends Exception { 7 | private final String s = getClass().getName(); 8 | 9 | @Override 10 | public boolean equals(Object o) { 11 | if (this == o) return true; 12 | if (o == null || getClass() != o.getClass()) return false; 13 | 14 | MockException that = (MockException) o; 15 | 16 | return s != null ? s.equals(that.s) : that.s == null; 17 | 18 | } 19 | 20 | @Override 21 | public int hashCode() { 22 | return s != null ? s.hashCode() : 0; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/arg/EScaleMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.arg; 18 | 19 | /** 20 | * How the scale attribute should be interpreted 21 | */ 22 | public enum EScaleMode { 23 | FACTOR, DP_WIDTH, DP_HEIGHT 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/scaling/ScaleAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.scaling; 18 | 19 | import java.awt.image.BufferedImage; 20 | 21 | /** 22 | * 23 | */ 24 | public interface ScaleAlgorithm { 25 | BufferedImage scale(BufferedImage imageToScale, int dWidth, int dHeight); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/ConverterCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters; 18 | 19 | import java.io.File; 20 | import java.util.List; 21 | 22 | /** 23 | * Callback to inform the callee of the result 24 | */ 25 | public interface ConverterCallback { 26 | 27 | void success(String log, List compressedImages); 28 | 29 | void failure(Exception e); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /src/test/java/at/favre/tools/dconvert/test/AIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package at.favre.tools.dconvert.test; 2 | 3 | import at.favre.tools.dconvert.arg.Arguments; 4 | import org.junit.Before; 5 | import org.junit.Rule; 6 | import org.junit.rules.TemporaryFolder; 7 | 8 | import java.io.File; 9 | import java.util.concurrent.CountDownLatch; 10 | 11 | /** 12 | * Basics of creating integration style tests (real input, real output) 13 | */ 14 | public class AIntegrationTest { 15 | static final long WAIT_SEC = 15; 16 | 17 | @Rule 18 | public TemporaryFolder temporaryFolder = new TemporaryFolder(); 19 | public Arguments arguments; 20 | public CountDownLatch latch; 21 | public File src; 22 | public File dst; 23 | 24 | @Before 25 | public void setUp() throws Exception { 26 | arguments = new Arguments.Builder(null, Arguments.DEFAULT_SCALE).threadCount(4).skipParamValidation(true).build(); 27 | latch = new CountDownLatch(1); 28 | src = temporaryFolder.newFolder("convert-test", "src"); 29 | dst = temporaryFolder.newFolder("convert-test", "out"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/descriptors/PostfixDescriptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.descriptors; 18 | 19 | /** 20 | * Created by PatrickF on 18.03.2016. 21 | */ 22 | public class PostfixDescriptor extends DensityDescriptor { 23 | public final String postFix; 24 | 25 | public PostfixDescriptor(float scale, String name, String postFix) { 26 | super(scale, name); 27 | this.postFix = postFix; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/exceptions/InvalidArgumentException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.exceptions; 18 | 19 | /** 20 | * Thrown if given arguments is invalid (e.g wrong range) 21 | */ 22 | public class InvalidArgumentException extends Exception { 23 | public InvalidArgumentException(String message) { 24 | super(message); 25 | } 26 | 27 | public InvalidArgumentException(String message, Throwable cause) { 28 | super(message, cause); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/exceptions/NinePatchException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.exceptions; 18 | 19 | /** 20 | * Exception in nine patch routine 21 | */ 22 | public class NinePatchException extends Exception { 23 | public NinePatchException() { 24 | } 25 | 26 | public NinePatchException(String message) { 27 | super(message); 28 | } 29 | 30 | public NinePatchException(String message, Throwable cause) { 31 | super(message, cause); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/at/favre/tools/dconvert/test/helper/MockProcessor.java: -------------------------------------------------------------------------------- 1 | package at.favre.tools.dconvert.test.helper; 2 | 3 | import at.favre.tools.dconvert.converters.Result; 4 | import at.favre.tools.dconvert.converters.postprocessing.IPostProcessor; 5 | import org.junit.Ignore; 6 | 7 | import java.io.File; 8 | import java.util.Collections; 9 | 10 | /** 11 | * For testing worker handlers 12 | */ 13 | @Ignore 14 | public class MockProcessor implements IPostProcessor { 15 | private long sleep = 66; 16 | private Exception exception = null; 17 | 18 | public MockProcessor() { 19 | } 20 | 21 | public MockProcessor(long sleep) { 22 | this.sleep = sleep; 23 | } 24 | 25 | public MockProcessor(Exception exception) { 26 | this.exception = exception; 27 | } 28 | 29 | @Override 30 | public Result process(File rawFile, boolean keepOriginal) { 31 | try { 32 | Thread.sleep(sleep); 33 | } catch (InterruptedException e) { 34 | e.printStackTrace(); 35 | } 36 | 37 | return new Result("test done", exception, Collections.singletonList(rawFile)); 38 | } 39 | 40 | @Override 41 | public boolean isSupported() { 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/ui/IPreferenceStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.ui; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | 21 | /** 22 | * Interface for preference store in the ui 23 | */ 24 | public interface IPreferenceStore { 25 | /** 26 | * Persistently saves the given argument 27 | * 28 | * @param arg 29 | */ 30 | void save(Arguments arg); 31 | 32 | /** 33 | * Gets the arguemnt object from the persistence store 34 | * 35 | * @return the arg or null if not set or could not be read 36 | */ 37 | Arguments get(); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/Result.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters; 18 | 19 | import java.io.File; 20 | import java.util.List; 21 | 22 | /** 23 | * Wrapper for a result 24 | */ 25 | public class Result { 26 | public final Exception exception; 27 | public final List processedFiles; 28 | public final String log; 29 | 30 | public Result(String log, Exception exception, List processedFiles) { 31 | this.log = log; 32 | this.exception = exception; 33 | this.processedFiles = processedFiles; 34 | } 35 | 36 | public Result(String log, List processedFiles) { 37 | this(log, null, processedFiles); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/arg/RoundingHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.arg; 18 | 19 | /** 20 | * Defines how float numbers will be rounded 21 | */ 22 | public class RoundingHandler { 23 | public enum Strategy { 24 | ROUND_HALF_UP, 25 | CEIL, 26 | FLOOR 27 | } 28 | 29 | private final Strategy strategy; 30 | 31 | public RoundingHandler(Strategy strategy) { 32 | this.strategy = strategy; 33 | } 34 | 35 | public long round(double value) { 36 | switch (strategy) { 37 | case CEIL: 38 | return Math.max(1, (long) Math.ceil(value)); 39 | case FLOOR: 40 | return Math.max(1, (long) Math.floor(value)); 41 | default: 42 | case ROUND_HALF_UP: 43 | return Math.max(1, Math.round(value)); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/IPlatformConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | 21 | import java.io.File; 22 | 23 | /** 24 | * Defines how an image will be converted for a specific platform and densities 25 | */ 26 | public interface IPlatformConverter { 27 | 28 | /** 29 | * Converts the given file to all needed densities 30 | * 31 | * @param srcImageFile source image file to be used as base to scale 32 | * @param arguments all tool args 33 | * @return result 34 | */ 35 | Result convert(File srcImageFile, Arguments arguments); 36 | 37 | /** 38 | * Cleans (ie. deletes) all dirs that would be generated with this converter and arguments 39 | * 40 | * @param arguments 41 | */ 42 | void clean(Arguments arguments); 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/postprocessing/IPostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.postprocessing; 18 | 19 | import at.favre.tools.dconvert.converters.Result; 20 | 21 | import java.io.File; 22 | 23 | /** 24 | * PostProcessor run after the main conversation on all files 25 | */ 26 | public interface IPostProcessor { 27 | String ORIG_POSTFIX = "_orig"; 28 | 29 | /** 30 | * Will process the given file. It is not necessary to spawn another thread for exectution 31 | * 32 | * @param rawFile to process 33 | * @param keepOriginal if true will not delete unprocessed file, but renames it to (filename)_orig.(extension) 34 | * @return optional log or output 35 | */ 36 | Result process(File rawFile, boolean keepOriginal); 37 | 38 | /** 39 | * @return true if this processor is supported with the current setup (e.g. tool is set in PATH) 40 | */ 41 | boolean isSupported(); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/arg/EOutputCompressionMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.arg; 18 | 19 | import java.util.ResourceBundle; 20 | 21 | /** 22 | * Different output compression modes 23 | */ 24 | public enum EOutputCompressionMode { 25 | SAME_AS_INPUT_PREF_PNG("enum.outcomp.SAME_AS_INPUT_PREF_PNG"), 26 | SAME_AS_INPUT_STRICT("enum.outcomp.SAME_AS_INPUT_STRICT"), 27 | AS_JPG("enum.outcomp.AS_JPG"), 28 | AS_PNG("enum.outcomp.AS_PNG"), 29 | AS_GIF("enum.outcomp.AS_GIF"), 30 | AS_BMP("enum.outcomp.AS_BMP"), 31 | AS_JPG_AND_PNG("enum.outcomp.AS_JPG_AND_PNG"); 32 | 33 | public final String rbKey; 34 | 35 | EOutputCompressionMode(String rbKey) { 36 | this.rbKey = rbKey; 37 | } 38 | 39 | public static EOutputCompressionMode getFromString(String i18nString, ResourceBundle bundle) { 40 | for (EOutputCompressionMode eOutputCompressionMode : EOutputCompressionMode.values()) { 41 | if (bundle.getString(eOutputCompressionMode.rbKey).equals(i18nString)) { 42 | return eOutputCompressionMode; 43 | } 44 | } 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/at/favre/tools/dconvert/test/WebConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package at.favre.tools.dconvert.test; 19 | 20 | import at.favre.tools.dconvert.arg.Arguments; 21 | import at.favre.tools.dconvert.arg.EPlatform; 22 | import at.favre.tools.dconvert.converters.WebConverter; 23 | 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.util.List; 27 | 28 | /** 29 | * Unit test of the {@link at.favre.tools.dconvert.converters.IPlatformConverter} for web 30 | */ 31 | public class WebConverterTest extends AConverterTest { 32 | @Override 33 | protected EPlatform getType() { 34 | return EPlatform.WEB; 35 | } 36 | 37 | @Override 38 | protected void checkOutDir(File dstDir, Arguments arguments, List files, EPlatform type) throws IOException { 39 | checkOutDirWeb(dstDir, arguments, files); 40 | } 41 | 42 | public static void checkOutDirWeb(File dstDir, Arguments arguments, List files) throws IOException { 43 | System.out.println("web-convert " + files); 44 | checkOutDirPostfixDescr(new File(dstDir, WebConverter.ROOT_FOLDER), arguments, files, WebConverter.getWebDescriptors()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/at/favre/tools/dconvert/test/WindowsConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package at.favre.tools.dconvert.test; 19 | 20 | import at.favre.tools.dconvert.arg.Arguments; 21 | import at.favre.tools.dconvert.arg.EPlatform; 22 | import at.favre.tools.dconvert.converters.WindowsConverter; 23 | 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.util.List; 27 | 28 | /** 29 | * Unit test of the {@link at.favre.tools.dconvert.converters.IPlatformConverter} for windows 30 | */ 31 | public class WindowsConverterTest extends AConverterTest { 32 | @Override 33 | protected EPlatform getType() { 34 | return EPlatform.WINDOWS; 35 | } 36 | 37 | @Override 38 | protected void checkOutDir(File dstDir, Arguments arguments, List files, EPlatform type) throws IOException { 39 | checkOutDirWindows(dstDir, arguments, files); 40 | } 41 | 42 | public static void checkOutDirWindows(File dstDir, Arguments arguments, List files) throws IOException { 43 | System.out.println("windows-convert " + files); 44 | checkOutDirPostfixDescr(new File(dstDir, WindowsConverter.ROOT_FOLDER), arguments, files, WindowsConverter.getWindowsDescriptors()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/util/LoadedImage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.util; 18 | 19 | import com.twelvemonkeys.imageio.metadata.CompoundDirectory; 20 | 21 | import javax.imageio.metadata.IIOMetadata; 22 | import java.awt.image.BufferedImage; 23 | import java.io.File; 24 | 25 | /** 26 | * Wraps a {@link java.awt.image.BufferedImage} and some other meta data 27 | */ 28 | 29 | public class LoadedImage { 30 | private final File sourceFile; 31 | private final BufferedImage image; 32 | private final IIOMetadata metadata; 33 | private final CompoundDirectory directory; 34 | 35 | public LoadedImage(File sourceFile, BufferedImage image, IIOMetadata metadata, CompoundDirectory directory) { 36 | this.sourceFile = sourceFile; 37 | this.image = image; 38 | this.metadata = metadata; 39 | this.directory = directory; 40 | } 41 | 42 | public File getSourceFile() { 43 | return sourceFile; 44 | } 45 | 46 | public BufferedImage getImage() { 47 | return image; 48 | } 49 | 50 | public IIOMetadata getMetadata() { 51 | return metadata; 52 | } 53 | 54 | public CompoundDirectory getExif() { 55 | return directory; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/postprocessing/MozJpegProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.postprocessing; 18 | 19 | import at.favre.tools.dconvert.arg.ImageType; 20 | import at.favre.tools.dconvert.converters.Result; 21 | import at.favre.tools.dconvert.util.PostProcessorUtil; 22 | 23 | import java.io.File; 24 | import java.util.Collections; 25 | 26 | /** 27 | * Optimzes jpeg with mozjpeg 28 | * https://github.com/mozilla/mozjpeg 29 | */ 30 | public class MozJpegProcessor extends APostProcessor { 31 | @Override 32 | public Result synchronizedProcess(File rawFile, boolean keepOriginal) { 33 | try { 34 | String[] args = new String[]{"jpegtran", "-outfile", "%%outFilePath%%", "-optimise", "-progressive", "-copy", "none", "%%sourceFilePath%%"}; 35 | return PostProcessorUtil.runImageOptimizer(rawFile, ImageType.JPG, args, keepOriginal); 36 | } catch (Exception e) { 37 | return new Result("could not execute post processor " + getClass().getSimpleName(), e, Collections.singletonList(rawFile)); 38 | } 39 | } 40 | 41 | @Override 42 | public boolean isSupported() { 43 | return PostProcessorUtil.canRunCmd(new String[]{"jpegtran", "-h"}); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/descriptors/DensityDescriptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.descriptors; 18 | 19 | /** 20 | * Base class for information on creating different densities for the platforms 21 | */ 22 | public class DensityDescriptor implements Comparable { 23 | public final float scale; 24 | public final String name; 25 | 26 | protected DensityDescriptor(float scale, String name) { 27 | this.scale = scale; 28 | this.name = name; 29 | } 30 | 31 | @Override 32 | public boolean equals(Object o) { 33 | if (this == o) return true; 34 | if (o == null || getClass() != o.getClass()) return false; 35 | 36 | DensityDescriptor that = (DensityDescriptor) o; 37 | 38 | if (Float.compare(that.scale, scale) != 0) return false; 39 | return !(name != null ? !name.equals(that.name) : that.name != null); 40 | 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | int result = (scale != +0.0f ? Float.floatToIntBits(scale) : 0); 46 | result = 31 * result + (name != null ? name.hashCode() : 0); 47 | return result; 48 | } 49 | 50 | @Override 51 | public int compareTo(DensityDescriptor o) { 52 | return Float.compare(scale, o.scale); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/descriptors/AndroidDensityDescriptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.descriptors; 18 | 19 | /** 20 | * Needed info to convert for Android 21 | */ 22 | public class AndroidDensityDescriptor extends DensityDescriptor { 23 | public final String folderName; 24 | 25 | public AndroidDensityDescriptor(float scale, String name, String folderName) { 26 | super(scale, name); 27 | this.folderName = folderName; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return "AndroidDensityDescription{" + 33 | "folderName='" + folderName + '\'' + 34 | '}'; 35 | } 36 | 37 | @Override 38 | public boolean equals(Object o) { 39 | if (this == o) return true; 40 | if (o == null || getClass() != o.getClass()) return false; 41 | if (!super.equals(o)) return false; 42 | 43 | AndroidDensityDescriptor that = (AndroidDensityDescriptor) o; 44 | 45 | return !(folderName != null ? !folderName.equals(that.folderName) : that.folderName != null); 46 | 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | int result = super.hashCode(); 52 | result = 31 * result + (folderName != null ? folderName.hashCode() : 0); 53 | return result; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/arg/EPlatform.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.arg; 18 | 19 | import at.favre.tools.dconvert.converters.*; 20 | 21 | import java.util.Collections; 22 | import java.util.HashSet; 23 | import java.util.List; 24 | import java.util.Set; 25 | import java.util.stream.Collectors; 26 | 27 | /** 28 | * Defines platforms to convert to 29 | */ 30 | public enum EPlatform { 31 | ANDROID(new AndroidConverter()), 32 | IOS(new IOSConverter()), 33 | WINDOWS(new WindowsConverter()), 34 | WEB(new WebConverter()); 35 | 36 | private final IPlatformConverter converter; 37 | 38 | EPlatform(IPlatformConverter converter) { 39 | this.converter = converter; 40 | } 41 | 42 | public IPlatformConverter getConverter() { 43 | return converter; 44 | } 45 | 46 | private static Set ALL; 47 | 48 | public static Set getAll() { 49 | if (ALL == null) { 50 | Set temp = new HashSet<>(EPlatform.values().length); 51 | for (EPlatform ePlatform : EPlatform.values()) { 52 | temp.add(ePlatform); 53 | } 54 | ALL = Collections.unmodifiableSet(temp); 55 | } 56 | return ALL; 57 | } 58 | 59 | public static List getAllConverters() { 60 | return getAll().stream().map(EPlatform::getConverter).collect(Collectors.toList()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # To let the CI execute the maven wrapper, use this command and push the change: 2 | # git update-index --chmod=+x mvnw 3 | 4 | language: java 5 | jdk: 6 | - oraclejdk11 7 | 8 | #required for launch4j to run 9 | before_script: 10 | - sudo apt-get update 11 | - sudo apt-get install libc6:i386 libstdc++6:i386 12 | 13 | script: 14 | - ./mvnw clean install -X 15 | - ./mvnw package 16 | 17 | after_success: 18 | - ./mvnw test jacoco:report coveralls:report 19 | 20 | deploy: 21 | - provider: releases 22 | api_key: 23 | secure: "4yszsIyM8p44pUCuuj6ZAbBgYFBkCdODaO6jUMJFRP/rBFsEa1QvqYJgrhXtmr/fZqDpnV8MLR93747eLShAiAgolcGePKAN6bH6J5fCDCp4+vOTnOsAdUFbBAzXnNybCZvlsceyLYHJZijfBjAw3U4bBZO99OGRx1EGlOY3X01Ahug2RS2O+viYeTGCHvcv83ULartGlsRSACDx27iw62PZ5mnEL1nTUd48ULbDO2D2mHhWakDJo0D0WIzZeTDVFgOR97ZnX0hWT8thAwhXt7TZBmrrAYrJVuBIA3pg2Gk0r6o2PElnp2pcnY8yzBZOMhtgqtRdUIzNVmrhQCgZeD7o9OVqmFYLrwlotbBhoMj+Ol956QWHTvrvwcJqiMLlb5MhHBTwp0gu8xNS3YVarNaBGd9w9GDE+zZ6p2USMTAMNAEeo6drKZLcFLpDxDY19r16Co/tThADOrXdoqGDDvJgDZVAwAM1dF4I+9GbIhDgdAsBJLDA5rE7d0eiEpGPxGy22P7jbxpH8IQUc/GSWfzuU+3e7V0TYnq4rrdfctNjt655KMfXY++X+6xhvY5EyThKaNz31q/4Gb4Y7yrrxn3gcjBXkBNoDD0pxSELfRtnzpg4M7RZyf3E//yZVcrEsxx5M1aPNMvHcQzOFDhWJeZ3peWMXY99HHVuMfdp1Qw=" 24 | file_glob: true 25 | file: 26 | - 'target/dconvert-*.jar' 27 | - 'target/dconvert.exe' 28 | - 'target/dconvert-gui.exe' 29 | skip_cleanup: true 30 | on: 31 | branch: master 32 | tags: true 33 | 34 | cache: 35 | directories: 36 | - $HOME/.m2 37 | 38 | notifications: 39 | slack: 40 | secure: "d5t2dP5H63qfY0umICPN4Tghr/TJIvni2i6NGEoNnq7xDqg24VV9+KvTpZ5b5W2XvLWIREwhZX0z2A2sct264Esc+VU6VGeuSebRu05AFVSL2w9hHQzh/PciicCAsIhVVf5nD0io3U5+/pQtOvMyEoBFlWS9EiRl7Lo5Gj1Iu48747IS7zF56ieI0iBFCSeT7Xb9L0+Hk7sjSKKeINDNz1UpqXFjwjDUYrwmA55ZCJPWNlDTzKdtjal9OE38SRy78UYe0R1UfEdEunaWrra2OTjpBT4d7Wc6t1ylcLGa/v6o8v59MA7Xy/PzXSIfHoJnqkMtZpC+yNEMvDmXPdaWTi16g4dV1+PvNFgZZqDQW9AuROzuoCTxDWjIzrPUaSfaG58cHTwt4hVHcOoJCJpfTMrX8QCEdQl6m/fIU+OXetlOy89a7A+oUl8gsNpArW7ML4n5qaxa0mTfayOruTMPBejO6gZLeOCyt4Nx8wmqdyVNsyVkr24P1FOj1fmR8pMu9IEc3uzu5fAZEPb4kR1HmydJTWgeCUhf+GKCcAkK+a/O8MsP66MaGbM9es3xr2hWFA2iTZokiXfDrdKMymjlEXQx9NEviLWe/oqyupG6O3Ee2oo1I9F2uFyeLhS5RQk6WhhXmeBR3ZX9PUcqiAFaU56XFHEuGOXXam3cEj2zbok=" 41 | email: false 42 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/postprocessing/APostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.postprocessing; 18 | 19 | import at.favre.tools.dconvert.converters.Result; 20 | 21 | import java.io.File; 22 | import java.util.Map; 23 | import java.util.concurrent.ConcurrentHashMap; 24 | import java.util.concurrent.locks.ReentrantLock; 25 | 26 | /** 27 | * Shared code among {@link IPostProcessor}. 28 | *

29 | * This helps to synchronize processors: will create a lock for each input file, 30 | * so that only 1 processor can process a file at a time 31 | */ 32 | public abstract class APostProcessor implements IPostProcessor { 33 | private static Map lockMap = new ConcurrentHashMap<>(); 34 | private static ReentrantLock administrationLock = new ReentrantLock(true); 35 | 36 | @Override 37 | public Result process(File rawFile, boolean keepOriginal) { 38 | try { 39 | 40 | administrationLock.lock(); 41 | if (!lockMap.containsKey(rawFile)) { 42 | lockMap.put(rawFile, new ReentrantLock(true)); 43 | } 44 | 45 | administrationLock.unlock(); 46 | 47 | lockMap.get(rawFile).lock(); 48 | 49 | return synchronizedProcess(rawFile, keepOriginal); 50 | } finally { 51 | lockMap.get(rawFile).unlock(); 52 | } 53 | } 54 | 55 | /** 56 | * This is the thread safe version of {@link #process(File, boolean)} 57 | * 58 | * @param rawFile 59 | * @param keepOriginal 60 | * @return 61 | */ 62 | protected abstract Result synchronizedProcess(File rawFile, boolean keepOriginal); 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/postprocessing/PngCrushProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.postprocessing; 18 | 19 | import at.favre.tools.dconvert.arg.ImageType; 20 | import at.favre.tools.dconvert.converters.Result; 21 | import at.favre.tools.dconvert.util.MiscUtil; 22 | import at.favre.tools.dconvert.util.PostProcessorUtil; 23 | 24 | import java.io.File; 25 | import java.util.Collections; 26 | 27 | /** 28 | * Calls pngcrush on a file 29 | */ 30 | public class PngCrushProcessor extends APostProcessor { 31 | private static final String[] DEFAULT_ARGS = new String[]{"-rem", "alla", "-rem", "text", "-rem", "gAMA", "-rem", "cHRM", "-rem", "iCCP", "-rem", "sRGB"}; 32 | private String[] additionalArgs; 33 | 34 | public PngCrushProcessor() { 35 | this(DEFAULT_ARGS); 36 | } 37 | 38 | public PngCrushProcessor(String[] additionalArgs) { 39 | this.additionalArgs = additionalArgs; 40 | } 41 | 42 | @Override 43 | public Result synchronizedProcess(File rawFile, boolean keepOriginal) { 44 | try { 45 | String[] args = MiscUtil.concat(MiscUtil.concat(new String[]{"pngcrush"}, additionalArgs), new String[]{"%%sourceFilePath%%", "%%outFilePath%%"}); 46 | return PostProcessorUtil.runImageOptimizer(rawFile, ImageType.PNG, args, keepOriginal); 47 | } catch (Exception e) { 48 | return new Result("could not execute post processor " + getClass().getSimpleName(), e, Collections.singletonList(rawFile)); 49 | } 50 | } 51 | 52 | @Override 53 | public boolean isSupported() { 54 | return PostProcessorUtil.canRunCmd(new String[]{"pngcrush", "-h"}); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/postprocessing/WebpProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.postprocessing; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | import at.favre.tools.dconvert.arg.ImageType; 21 | import at.favre.tools.dconvert.converters.Result; 22 | import at.favre.tools.dconvert.util.MiscUtil; 23 | import at.favre.tools.dconvert.util.PostProcessorUtil; 24 | 25 | import java.io.File; 26 | import java.util.Collections; 27 | 28 | /** 29 | * Converts pngs/jpegs to lossless/lossy webp 30 | */ 31 | public class WebpProcessor extends APostProcessor { 32 | 33 | @Override 34 | public Result synchronizedProcess(File rawFile, boolean keepOriginal) { 35 | try { 36 | ImageType compression = Arguments.getImageType(rawFile); 37 | String[] additionalArgs; 38 | if (compression == ImageType.PNG || compression == ImageType.GIF) { 39 | additionalArgs = new String[]{"-lossless", "-alpha_filter", "best", "-m", "6"}; 40 | } else if (compression == ImageType.JPG) { 41 | additionalArgs = new String[]{"-m", "6", "-q", "90"}; 42 | } else { 43 | return null; 44 | } 45 | 46 | String[] finalArg = MiscUtil.concat(MiscUtil.concat(new String[]{"cwebp"}, additionalArgs), new String[]{"%%sourceFilePath%%", "-o", "%%outFilePath%%"}); 47 | 48 | return PostProcessorUtil.runImageOptimizer(rawFile, compression, finalArg, keepOriginal, "webp"); 49 | } catch (Exception e) { 50 | return new Result("could not execute post processor " + getClass().getSimpleName(), e, Collections.singletonList(rawFile)); 51 | } 52 | } 53 | 54 | @Override 55 | public boolean isSupported() { 56 | return PostProcessorUtil.canRunCmd(new String[]{"cwebp", "-h"}); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/arg/ImageType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.arg; 18 | 19 | /** 20 | * Supported image types 21 | */ 22 | public enum ImageType { 23 | 24 | JPG(ECompression.JPG, ECompression.JPG, new String[]{"jpeg", "jpg"}, true), 25 | PNG(ECompression.PNG, ECompression.PNG, new String[]{"png"}, true), 26 | GIF(ECompression.GIF, ECompression.PNG, new String[]{"gif"}, true), 27 | SVG(ECompression.PNG, ECompression.PNG, new String[]{"svg"}, true), 28 | PSD(ECompression.PNG, ECompression.PNG, new String[]{"psd"}, true), //adobe photoshop 29 | TIFF(ECompression.TIFF, ECompression.PNG, new String[]{"tif", "tiff"}, true), //Tagged Image File Format 30 | BMP(ECompression.BMP, ECompression.PNG, new String[]{"bmp"}, true); // bitmap image file or device independent bitmap (DIB) 31 | 32 | public final ECompression outCompressionStrict; 33 | public final ECompression outCompressionCompat; 34 | public final String[] extensions; 35 | public final boolean supportRead; 36 | 37 | ImageType(ECompression outCompressionStrict, ECompression outCompressionCompat, String[] extensions, boolean supportRead) { 38 | this.outCompressionStrict = outCompressionStrict; 39 | this.outCompressionCompat = outCompressionCompat; 40 | this.extensions = extensions; 41 | this.supportRead = supportRead; 42 | } 43 | 44 | /** 45 | * Supported image compression types 46 | */ 47 | public enum ECompression { 48 | JPG(false, "jpg"), PNG(true, "png"), GIF(true, "gif"), TIFF(false, "tif"), BMP(false, "bmp"); 49 | 50 | public final boolean hasTransparency; 51 | public final String extension; 52 | 53 | ECompression(boolean hasTransparency, String extension) { 54 | this.hasTransparency = hasTransparency; 55 | this.extension = extension; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/resources/svg_example_512.svg: -------------------------------------------------------------------------------- 1 | 6 | 7 | SVG Logo 8 | Designed for the SVG Logo Contest in 2006 by Harvey Rayner, and adopted by W3C in 2009. It is available under the Creative Commons license for those who have an SVG product or who are using SVG on their site. 9 | 10 | 11 | 15 | 16 | SVG Logo 17 | 14-08-2009 18 | 19 | W3C 20 | Harvey Rayner, designer 21 | 22 | See document description 23 | 24 | image/svg+xml 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/test/java/at/favre/tools/dconvert/test/CLISystemTest.java: -------------------------------------------------------------------------------- 1 | package at.favre.tools.dconvert.test; 2 | 3 | import at.favre.tools.dconvert.Main; 4 | import at.favre.tools.dconvert.arg.Arguments; 5 | import at.favre.tools.dconvert.ui.CLIInterpreter; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.io.File; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | /** 14 | * System test through command line interface 15 | */ 16 | public class CLISystemTest extends AIntegrationTest { 17 | private String defaultArgRaw; 18 | 19 | @Before 20 | public void setUp() throws Exception { 21 | super.setUp(); 22 | defaultArgRaw = "-src \"" + src.getAbsolutePath() + "\" -dst \"" + dst.getAbsolutePath() + "\" -scale 4"; 23 | 24 | } 25 | 26 | @Test 27 | public void testZeroFilesInput() throws Exception { 28 | Arguments arg = CLIInterpreter.parse(CLIParserTest.asArgArray(defaultArgRaw)); 29 | Main.main(CLIParserTest.asArgArray(defaultArgRaw)); 30 | AConverterTest.checkMultiPlatformConvert(dst, arg, Collections.emptyList()); 31 | } 32 | 33 | @Test 34 | public void testSingleFileIosPlatformConverter() throws Exception { 35 | defaultArgRaw += " -" + CLIInterpreter.PLATFORM_ARG + " ios"; 36 | List files = AConverterTest.copyToTestPath(src, "png_example1_alpha_144.png"); 37 | Arguments arg = CLIInterpreter.parse(CLIParserTest.asArgArray(defaultArgRaw)); 38 | Main.main(CLIParserTest.asArgArray(defaultArgRaw)); 39 | IOSConverterTest.checkOutDirIos(dst, arg, files); 40 | } 41 | 42 | @Test 43 | public void testAndroidPlatformConverter() throws Exception { 44 | List files = AConverterTest.copyToTestPath(src, "png_example3_alpha_128.png", "png_example1_alpha_144.png", "jpg_example2_512.jpg", "gif_example_640.gif", "png_example4_500.png", "psd_example_827.psd"); 45 | defaultArgRaw += " -" + CLIInterpreter.PLATFORM_ARG + " android"; 46 | Arguments arg = CLIInterpreter.parse(CLIParserTest.asArgArray(defaultArgRaw)); 47 | Main.main(CLIParserTest.asArgArray(defaultArgRaw)); 48 | AndroidConverterTest.checkOutDirAndroid(dst, arg, files); 49 | } 50 | 51 | @Test 52 | public void testAllPlatformConverter() throws Exception { 53 | List files = AConverterTest.copyToTestPath(src, "png_example3_alpha_128.png", "png_example1_alpha_144.png", "jpg_example2_512.jpg"); 54 | defaultArgRaw += " -" + CLIInterpreter.PLATFORM_ARG + " all"; 55 | Arguments arg = CLIInterpreter.parse(CLIParserTest.asArgArray(defaultArgRaw)); 56 | Main.main(CLIParserTest.asArgArray(defaultArgRaw)); 57 | AConverterTest.checkMultiPlatformConvert(dst, arg, files); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | # Releases 2 | 3 | ## v1.0.0-alpha7 4 | * update to jdk11 5 | 6 | ## v1.0.0-alpha6 7 | * update various dependencies and plugins 8 | 9 | ## v1.0.0-alpha5 10 | * update various dependencies and plugins 11 | * switch to maven wrapper 12 | 13 | ## v1.0.0-alpha4 14 | * fixes ios content json format 15 | 16 | ## v1.0.0-alpha3 17 | * fixes bug in ninepatch where lanczos3 would not scale due to min dimension restrictions 18 | * adds console and gui exe 19 | 20 | ## v1.0.0-alpha1 21 | * greatly refactor scaling options and logic making it possible to select specific scaling algorithm 22 | * makes project compatible with travis ci 23 | * updates TwelveMonkeys image lib to 3.3.1 24 | 25 | Note: this is a early alpha preview - use at your own risk 26 | 27 | ## v0.9.2 28 | * exchanges imgsclr lib with thumbnailator for scaling resulting in much better quality 29 | 30 | ## v0.9.1 31 | * new clean before convert option 32 | * "simpler" simple mode 33 | 34 | ## v0.9.0 35 | * adds web converter suitable for css image-set 36 | * can select abitratry permutation of converters in gui 37 | * ios converter now uses @2x, @3x postfix 38 | * ios converter gets option for either .imageset or root folder 39 | * adds gui advanced mode 40 | * ui polishing 41 | * add better cmd progress visualisation 42 | 43 | ## v0.8.1 44 | * new button that will open file explorer with destination folder 45 | * removes verbose from GUI 46 | * jpeg quality can now also be 0.75,0.85 and 0.95 47 | * add screen size check 48 | 49 | ## v0.8.0 50 | * adds keep original flag (for post processors) 51 | * refactors threading design - post processors are now parallel 52 | * fix ninepatch calculation (using net image as dimensions) 53 | * adds mozJpeg lossless jpg compression post processor 54 | * some error handling if post processor cmd application is missing 55 | 56 | ## v0.7.0 57 | * adds 9-patch support (needs to have .9.png extension and out compression must be png; also only works in Android converter) 58 | * adds ability to I18n cmd & gui 59 | * adds ui tests with testfx 60 | 61 | ## v0.6.2 62 | * adds Windows converter 63 | 64 | ## v0.6.1 65 | * adds fixed height dp mode 66 | * fixes some bugs 67 | 68 | ## v0.6.0 69 | * adds scale mode: fixed width in dp 70 | * adds unit tests 71 | 72 | ## v0.5.1 73 | * support for svg rendering 74 | * additional support for file types: psd, tiff and bmp 75 | * new same-strict mode in out compression 76 | * ui will now persist settings 77 | * fixed some bugs 78 | 79 | > Because svg-renderer batik is used which needs a lot of dependencies, the jar grew to 5MB - use of proguard for future releases is researched 80 | 81 | ## v0.4.0 82 | * better resize algo 83 | * adds anti aliasing 84 | * adds tvdpi 85 | *fixes jpeg with wrong colors output 86 | 87 | ## v0.3.0 88 | 89 | * fixes pom 90 | 91 | ## v0.2.0 92 | initial release 93 | -------------------------------------------------------------------------------- /.github/workflows/build_deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy with Maven 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '*' # Trigger on all tags 9 | pull_request: { } 10 | 11 | env: 12 | SONARQUBE_PROJECT: patrickfav_density-converter 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout Code 20 | uses: actions/checkout@v3 21 | with: 22 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 23 | - name: Cache SonarCloud packages 24 | uses: actions/cache@v3 25 | with: 26 | path: ~/.sonar/cache 27 | key: ${{ runner.os }}-sonar 28 | restore-keys: ${{ runner.os }}-sonar 29 | - name: Cache Maven 30 | uses: actions/cache@v3 31 | with: 32 | path: ~/.m2 33 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 34 | restore-keys: ${{ runner.os }}-m2 35 | - name: Set up JDK 11 36 | uses: actions/setup-java@v3 37 | with: 38 | java-version: '11' 39 | distribution: 'zulu' 40 | java-package: jdk+fx # optional (jdk, jre, jdk+fx or jre+fx) - defaults to jdk 41 | - name: Build with Maven 42 | run: ./mvnw -B clean verify -DcommonConfig.jarSign.skip=true 43 | - name: Analyze with SonaQube 44 | if: ${{ github.actor != 'dependabot[bot]' }} 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 47 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 48 | run: mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=$SONARQUBE_PROJECT 49 | 50 | deploy: 51 | needs: [build] 52 | if: startsWith(github.ref, 'refs/tags/') 53 | runs-on: ubuntu-latest 54 | 55 | steps: 56 | - uses: actions/checkout@v3 57 | - name: Cache Maven 58 | uses: actions/cache@v3 59 | with: 60 | path: ~/.m2 61 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 62 | restore-keys: ${{ runner.os }}-m2 63 | - name: Set up Maven Central Repository 64 | uses: actions/setup-java@v3 65 | with: 66 | java-version: '11' 67 | distribution: 'zulu' 68 | java-package: jdk+fx # optional (jdk, jre, jdk+fx or jre+fx) - defaults to jdk 69 | - name: Build with Maven 70 | run: ./mvnw -B verify -DskipTests 71 | - name: Create and upload Github Release 72 | uses: xresloader/upload-to-github-release@v1 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 75 | with: 76 | file: "target/dconvert-*.jar;target/dconvert.exe;target/dconvert-gui.exe;target/checksum-*.txt" 77 | tags: true 78 | draft: false 79 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/ui/SerializePreferenceStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.ui; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | 21 | import java.io.*; 22 | import java.util.Base64; 23 | import java.util.prefs.Preferences; 24 | 25 | /** 26 | * Simple persistence store for UI 27 | */ 28 | public class SerializePreferenceStore implements IPreferenceStore { 29 | public static final String ARGS_KEY = "args"; 30 | private Preferences prefs; 31 | 32 | public SerializePreferenceStore() { 33 | this.prefs = Preferences.userNodeForPackage(GUI.class); 34 | } 35 | 36 | @Override 37 | public void save(Arguments arg) { 38 | try { 39 | prefs.put(ARGS_KEY, serialize(arg)); 40 | } catch (Exception e) { 41 | prefs.remove(ARGS_KEY); 42 | e.printStackTrace(); 43 | } 44 | } 45 | 46 | @Override 47 | public Arguments get() { 48 | try { 49 | String saved = prefs.get(ARGS_KEY, null); 50 | if (saved == null) { 51 | return null; 52 | } 53 | 54 | Object out = unserialize(saved); 55 | return (Arguments) out; 56 | } catch (Exception e) { 57 | prefs.remove(ARGS_KEY); 58 | e.printStackTrace(); 59 | return null; 60 | } 61 | } 62 | 63 | private static String serialize(Serializable obj) throws IOException { 64 | ObjectOutput out = null; 65 | try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { 66 | out = new ObjectOutputStream(bos); 67 | out.writeObject(obj); 68 | return Base64.getEncoder().encodeToString(bos.toByteArray()); 69 | } finally { 70 | try { 71 | if (out != null) { 72 | out.close(); 73 | } 74 | } catch (IOException ex) { 75 | // ignore close exception 76 | } 77 | } 78 | } 79 | 80 | private static Object unserialize(String base64Obj) throws IOException, ClassNotFoundException { 81 | try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64Obj)))) { 82 | return in.readObject(); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/ui/GUI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.ui; 18 | 19 | import javafx.application.Application; 20 | import javafx.fxml.FXMLLoader; 21 | import javafx.scene.Parent; 22 | import javafx.scene.Scene; 23 | import javafx.scene.image.Image; 24 | import javafx.stage.Stage; 25 | 26 | import java.awt.*; 27 | import java.io.IOException; 28 | import java.util.Locale; 29 | import java.util.ResourceBundle; 30 | 31 | /** 32 | * Main GUI Class 33 | */ 34 | public class GUI extends Application { 35 | static int MIN_HEIGHT = 860; 36 | 37 | public void launchApp(String[] args) { 38 | launch(args); 39 | } 40 | 41 | @Override 42 | public void start(Stage primaryStage) throws Exception { 43 | setup(primaryStage, new SerializePreferenceStore(), Toolkit.getDefaultToolkit().getScreenSize()); 44 | primaryStage.show(); 45 | } 46 | 47 | public static GUIController setup(Stage primaryStage, IPreferenceStore store, Dimension screenSize) throws IOException { 48 | primaryStage.setTitle("Density Converter"); 49 | 50 | ResourceBundle bundle = ResourceBundle.getBundle("bundles.strings", Locale.getDefault()); 51 | 52 | FXMLLoader loader = new FXMLLoader(GUI.class.getClassLoader().getResource("main.fxml")); 53 | loader.setResources(bundle); 54 | Parent root = loader.load(); 55 | GUIController controller = loader.getController(); 56 | controller.onCreate(primaryStage, store, bundle); 57 | 58 | if (screenSize.getHeight() <= 768) { 59 | MIN_HEIGHT = 740; 60 | } 61 | 62 | Scene scene = new Scene(root, 600, MIN_HEIGHT); 63 | primaryStage.setScene(scene); 64 | primaryStage.setResizable(true); 65 | primaryStage.setMinWidth(400); 66 | primaryStage.setMinHeight(500); 67 | primaryStage.getIcons().add(new Image("img/density_converter_icon_16.png")); 68 | primaryStage.getIcons().add(new Image("img/density_converter_icon_24.png")); 69 | primaryStage.getIcons().add(new Image("img/density_converter_icon_48.png")); 70 | primaryStage.getIcons().add(new Image("img/density_converter_icon_64.png")); 71 | primaryStage.getIcons().add(new Image("img/density_converter_icon_128.png")); 72 | primaryStage.getIcons().add(new Image("img/density_converter_icon_256.png")); 73 | 74 | return controller; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/scaling/NaiveGraphics2dAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.scaling; 18 | 19 | import java.awt.*; 20 | import java.awt.image.BufferedImage; 21 | 22 | /** 23 | * Using java natives Graphics2d with best possible renderhints 24 | */ 25 | public class NaiveGraphics2dAlgorithm implements ScaleAlgorithm { 26 | private Object interpolationValue; 27 | 28 | public NaiveGraphics2dAlgorithm(Object interpolationValue) { 29 | this.interpolationValue = interpolationValue; 30 | } 31 | 32 | @Override 33 | public BufferedImage scale(BufferedImage imageToScale, int dWidth, int dHeight) { 34 | 35 | int imageType = BufferedImage.TYPE_INT_RGB; 36 | if (imageToScale.getType() != BufferedImage.TYPE_INT_RGB) { 37 | imageType = BufferedImage.TYPE_INT_ARGB; 38 | } 39 | 40 | BufferedImage scaledImage = new BufferedImage(dWidth, dHeight, imageType); 41 | Graphics2D graphics2D = scaledImage.createGraphics(); 42 | graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolationValue); 43 | graphics2D.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); 44 | graphics2D.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); 45 | graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 46 | graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 47 | 48 | graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null); 49 | 50 | graphics2D.dispose(); 51 | 52 | return scaledImage; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "NaiveGraphics2d[" + interpolationValue + ']'; 58 | } 59 | 60 | @Override 61 | public boolean equals(Object o) { 62 | if (this == o) return true; 63 | if (o == null || getClass() != o.getClass()) return false; 64 | 65 | NaiveGraphics2dAlgorithm that = (NaiveGraphics2dAlgorithm) o; 66 | 67 | return interpolationValue != null ? interpolationValue.equals(that.interpolationValue) : that.interpolationValue == null; 68 | 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | return interpolationValue != null ? interpolationValue.hashCode() : 0; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/scaling/ResampleAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.scaling; 18 | 19 | import com.mortennobel.imagescaling.ResampleFilter; 20 | import com.mortennobel.imagescaling.ResampleOp; 21 | 22 | import java.awt.image.BufferedImage; 23 | 24 | /** 25 | * Wrapper for Resample Algos from Nobel's Lib 26 | */ 27 | public class ResampleAlgorithm implements ScaleAlgorithm { 28 | private ResampleFilter filter; 29 | 30 | public ResampleAlgorithm(ResampleFilter filter) { 31 | this.filter = filter; 32 | } 33 | 34 | @Override 35 | public BufferedImage scale(BufferedImage imageToScale, int dWidth, int dHeight) { 36 | ResampleOp resizeOp = new ResampleOp(dWidth, dHeight); 37 | resizeOp.setFilter(filter); 38 | return resizeOp.filter(imageToScale, null); 39 | } 40 | 41 | public static class LanczosFilter implements ResampleFilter { 42 | private static final float PI_FLOAT = (float) Math.PI; 43 | private final float radius; 44 | 45 | public LanczosFilter(float radius) { 46 | this.radius = radius; 47 | } 48 | 49 | private float sincModified(float value) { 50 | return ((float) Math.sin(value)) / value; 51 | } 52 | 53 | public final float apply(float value) { 54 | if (value == 0) { 55 | return 1.0f; 56 | } 57 | if (value < 0.0f) { 58 | value = -value; 59 | } 60 | 61 | if (value < radius) { 62 | value *= PI_FLOAT; 63 | return sincModified(value) * sincModified(value / radius); 64 | } else { 65 | return 0.0f; 66 | } 67 | } 68 | 69 | public float getSamplingRadius() { 70 | return radius; 71 | } 72 | 73 | public String getName() { 74 | return "Lanczos" + (int) radius; 75 | } 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return "ResampleAlgorithm[" + filter.getName() + ']'; 81 | } 82 | 83 | @Override 84 | public boolean equals(Object o) { 85 | if (this == o) return true; 86 | if (o == null || getClass() != o.getClass()) return false; 87 | 88 | ResampleAlgorithm that = (ResampleAlgorithm) o; 89 | 90 | return filter != null ? filter.equals(that.filter) : that.filter == null; 91 | 92 | } 93 | 94 | @Override 95 | public int hashCode() { 96 | return filter != null ? filter.hashCode() : 0; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | import at.favre.tools.dconvert.ui.CLIInterpreter; 21 | import at.favre.tools.dconvert.ui.GUI; 22 | import at.favre.tools.dconvert.util.MiscUtil; 23 | 24 | import java.io.IOException; 25 | import java.util.List; 26 | 27 | /** 28 | * Entry point of the app. Use arg -h to get help. 29 | */ 30 | public final class Main { 31 | private Main() { 32 | } 33 | 34 | public static void main(String[] rawArgs) { 35 | 36 | if (rawArgs.length < 1) { 37 | new GUI().launchApp(rawArgs); 38 | return; 39 | } 40 | 41 | Arguments args = CLIInterpreter.parse(rawArgs); 42 | 43 | if (args == null) { 44 | return; 45 | } else if (args == Arguments.START_GUI) { 46 | System.out.println("start gui"); 47 | new GUI().launchApp(rawArgs); 48 | return; 49 | } 50 | 51 | System.out.println("start converting " + args.filesToProcess.size() + " files"); 52 | 53 | new DConvert().execute(args, true, new DConvert.HandlerCallback() { 54 | @Override 55 | public void onProgress(float progress) { 56 | try { 57 | System.out.write(MiscUtil.getCmdProgressBar(progress).getBytes()); 58 | } catch (IOException e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | 63 | @Override 64 | public void onFinished(int finishedJobs, List exceptions, long time, boolean haltedDuringProcess, String log) { 65 | System.out.print(MiscUtil.getCmdProgressBar(1f)); 66 | 67 | System.out.println(""); 68 | 69 | if (args.verboseLog) { 70 | System.out.println("Log:"); 71 | System.out.println(log); 72 | } 73 | 74 | if (haltedDuringProcess) { 75 | System.err.println("abort due to error"); 76 | } 77 | if (exceptions.size() > 0) { 78 | System.err.println("found " + exceptions.size() + " errors during execution"); 79 | if (args.verboseLog) { 80 | for (Exception exception : exceptions) { 81 | System.err.println("\terror: " + exception.getMessage()); 82 | exception.printStackTrace(); 83 | } 84 | } 85 | } 86 | System.out.println("execution finished (" + time + "ms) with " + finishedJobs + " finsihed jobs and " + exceptions.size() + " errors"); 87 | } 88 | }); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/WebConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | import at.favre.tools.dconvert.arg.ImageType; 21 | import at.favre.tools.dconvert.converters.descriptors.PostfixDescriptor; 22 | import at.favre.tools.dconvert.util.MiscUtil; 23 | 24 | import java.awt.*; 25 | import java.io.File; 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | /** 30 | * Converts and creates css image-set style images 31 | */ 32 | public class WebConverter extends APlatformConverter { 33 | private static final String WEB_FOLDER_NAME = "web"; 34 | public static final String ROOT_FOLDER = "img"; 35 | 36 | @Override 37 | public List usedOutputDensities(Arguments arguments) { 38 | return getWebDescriptors(); 39 | } 40 | 41 | public static List getWebDescriptors() { 42 | List list = new ArrayList<>(); 43 | list.add(new PostfixDescriptor(1, "1x", "-1x")); 44 | list.add(new PostfixDescriptor(2f, "2x", "-2x")); 45 | return list; 46 | } 47 | 48 | @Override 49 | public String getConverterName() { 50 | return "web-converter"; 51 | } 52 | 53 | @Override 54 | public File createMainSubFolder(File destinationFolder, String targetImageFileName, Arguments arguments) { 55 | if (arguments.platform.size() > 1) { 56 | destinationFolder = MiscUtil.createAndCheckFolder(new File(destinationFolder, WEB_FOLDER_NAME).getAbsolutePath(), arguments.dryRun); 57 | } 58 | return MiscUtil.createAndCheckFolder(new File(destinationFolder, ROOT_FOLDER).getAbsolutePath(), arguments.dryRun); 59 | } 60 | 61 | @Override 62 | public File createFolderForOutputFile(File mainSubFolder, PostfixDescriptor density, Dimension dimension, String targetFileName, Arguments arguments) { 63 | return mainSubFolder; 64 | } 65 | 66 | @Override 67 | public String createDestinationFileNameWithoutExtension(PostfixDescriptor density, Dimension dimension, String targetFileName, Arguments arguments) { 68 | return targetFileName + density.postFix; 69 | } 70 | 71 | @Override 72 | public void onPreExecute(File dstFolder, String targetFileName, List densityDescriptions, ImageType imageType, Arguments arguments) throws Exception { 73 | 74 | } 75 | 76 | @Override 77 | public void onPostExecute(Arguments arguments) { 78 | 79 | } 80 | 81 | @Override 82 | public void clean(Arguments arguments) { 83 | if (arguments.platform.size() == 1) { 84 | MiscUtil.deleteFolder(new File(arguments.dst, ROOT_FOLDER)); 85 | } else { 86 | MiscUtil.deleteFolder(new File(new File(arguments.dst, WEB_FOLDER_NAME), ROOT_FOLDER)); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/WindowsConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | import at.favre.tools.dconvert.arg.ImageType; 21 | import at.favre.tools.dconvert.converters.descriptors.PostfixDescriptor; 22 | import at.favre.tools.dconvert.util.MiscUtil; 23 | 24 | import java.awt.*; 25 | import java.io.File; 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | /** 30 | * Needed info to convert for Windows 31 | */ 32 | public class WindowsConverter extends APlatformConverter { 33 | private static final String WINDOWS_FOLDER_NAME = "windows"; 34 | public static final String ROOT_FOLDER = "Assets"; 35 | 36 | @Override 37 | public List usedOutputDensities(Arguments arguments) { 38 | return getWindowsDescriptors(); 39 | } 40 | 41 | public static List getWindowsDescriptors() { 42 | List list = new ArrayList<>(); 43 | list.add(new PostfixDescriptor(1, "100%", ".scale-100")); 44 | list.add(new PostfixDescriptor(1.4f, "140%", ".scale-140")); 45 | list.add(new PostfixDescriptor(1.8f, "180%", ".scale-180")); 46 | list.add(new PostfixDescriptor(2.4f, "240%", ".scale-240")); 47 | return list; 48 | } 49 | 50 | @Override 51 | public String getConverterName() { 52 | return "windows-converter"; 53 | } 54 | 55 | @Override 56 | public File createMainSubFolder(File destinationFolder, String targetImageFileName, Arguments arguments) { 57 | if (arguments.platform.size() > 1) { 58 | destinationFolder = MiscUtil.createAndCheckFolder(new File(destinationFolder, WINDOWS_FOLDER_NAME).getAbsolutePath(), arguments.dryRun); 59 | } 60 | return MiscUtil.createAndCheckFolder(new File(destinationFolder, WindowsConverter.ROOT_FOLDER).getAbsolutePath(), arguments.dryRun); 61 | } 62 | 63 | @Override 64 | public File createFolderForOutputFile(File mainSubFolder, PostfixDescriptor density, Dimension dimension, String targetFileName, Arguments arguments) { 65 | return mainSubFolder; 66 | } 67 | 68 | @Override 69 | public String createDestinationFileNameWithoutExtension(PostfixDescriptor density, Dimension dimension, String targetFileName, Arguments arguments) { 70 | return targetFileName + density.postFix; 71 | } 72 | 73 | @Override 74 | public void onPreExecute(File dstFolder, String targetFileName, List densityDescriptions, ImageType imageType, Arguments arguments) throws Exception { 75 | 76 | } 77 | 78 | @Override 79 | public void onPostExecute(Arguments arguments) { 80 | 81 | } 82 | 83 | @Override 84 | public void clean(Arguments arguments) { 85 | if (arguments.platform.size() == 1) { 86 | MiscUtil.deleteFolder(new File(arguments.dst, ROOT_FOLDER)); 87 | } else { 88 | MiscUtil.deleteFolder(new File(new File(arguments.dst, WINDOWS_FOLDER_NAME), ROOT_FOLDER)); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/resources/style.css: -------------------------------------------------------------------------------- 1 | .footnote { 2 | -fx-font-size: 0.7em; 3 | -fx-text-fill: #8a8e8f; 4 | } 5 | 6 | .console { 7 | -fx-font-size: 0.85em; 8 | -fx-text-fill: #333; 9 | -fx-font-family: monospace; 10 | } 11 | 12 | .label_main { 13 | -fx-font-size: 1.15em; 14 | -fx-font-weight: bold; 15 | } 16 | 17 | .label_prominent { 18 | -fx-font-size: 1.15em; 19 | -fx-font-weight: bold; 20 | } 21 | 22 | .button-sm { 23 | -fx-font-size: 0.8em; 24 | -fx-padding: 0.3em; 25 | } 26 | 27 | .why-info { 28 | -fx-font-size: 0.8em; 29 | -fx-font-weight: normal; 30 | -fx-underline: true; 31 | } 32 | 33 | .why-info:hover { 34 | -fx-underline: false; 35 | -fx-cursor: hand; 36 | } 37 | 38 | .button-convert { 39 | -fx-font-size: 1.5em; 40 | -fx-font-weight: bold; 41 | -fx-min-height: 3em; 42 | } 43 | 44 | .progress-convert { 45 | -fx-font-size: 1.5em; 46 | -fx-opacity: 0.3; 47 | -fx-min-height: 3em; 48 | } 49 | 50 | .progress-convert > .track { 51 | -fx-background-color: transparent; 52 | } 53 | 54 | /******************************************************************************* 55 | * * 56 | * Button & ToggleButton * 57 | * * 58 | ******************************************************************************/ 59 | 60 | .toggle-button-platform { 61 | -fx-background-color: -flatter-red; 62 | -fx-background-insets: 0.0; 63 | -fx-border-width: 0.0; 64 | -fx-padding: 0.6em; 65 | -fx-text-fill: -fx-text-base-color; 66 | -fx-alignment: CENTER; 67 | -fx-content-display: LEFT; 68 | -fx-font-size: 1.1em; 69 | -fx-background-radius: 0; 70 | } 71 | 72 | .toggle-button-platform-left { 73 | -fx-background-radius: 2px 0 0 2px; 74 | } 75 | 76 | .toggle-button-platform-right { 77 | -fx-background-radius: 0 2px 2px 0; 78 | } 79 | 80 | .toggle-button-platform:focused { 81 | -fx-background-color: -flatter-red; 82 | } 83 | 84 | .toggle-button-platform:focused:selected { 85 | -fx-background-color: derive(-flatter-red, 10.0%), derive(-flatter-red, -20.0%); 86 | -fx-background-insets: 0.0, 0px; 87 | -fx-font-weight: bold; 88 | -fx-effect: innershadow(gaussian, rgba(0, 0, 0, 0.25), 4, 0, 0, 2); 89 | } 90 | 91 | .toggle-button-platform:hover { 92 | -fx-cursor: hand; 93 | } 94 | 95 | .toggle-button-platform:armed, 96 | .toggle-button-platform:selected { 97 | -fx-background-color: derive(-flatter-red, -20.0%); 98 | -fx-font-weight: bold; 99 | -fx-effect: innershadow(gaussian, rgba(0, 0, 0, 0.25), 4, 0, 0, 2); 100 | } 101 | 102 | .toggle-button-platform-android { 103 | -fx-graphic: url("img/android.png"); 104 | } 105 | 106 | .toggle-button-platform-ios { 107 | -fx-graphic: url("img/iphone.png"); 108 | } 109 | 110 | .toggle-button-platform-win { 111 | -fx-graphic: url("img/windows.png"); 112 | } 113 | 114 | .toggle-button-platform-web { 115 | -fx-graphic: url("img/web.png"); 116 | } 117 | 118 | .toggle-button-platform-android:focused:selected, 119 | .toggle-button-platform-android:armed, 120 | .toggle-button-platform-android:selected { 121 | /*-fx-text-fill: #97C024;*/ 122 | } 123 | 124 | .toggle-button-platform-ios:focused:selected, 125 | .toggle-button-platform-ios:armed, 126 | .toggle-button-platform-ios:selected { 127 | /*-fx-text-fill: #B6B8B7;*/ 128 | } 129 | 130 | .toggle-button-platform-win:focused:selected, 131 | .toggle-button-platform-win:armed, 132 | .toggle-button-platform-win:selected { 133 | /*-fx-text-fill: #299cf8;*/ 134 | } 135 | 136 | .toggle-button-platform-web:focused:selected, 137 | .toggle-button-platform-web:armed, 138 | .toggle-button-platform-web:selected { 139 | /*-fx-text-fill: #F16529;*/ 140 | } -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/util/MiscUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.util; 18 | 19 | import java.io.File; 20 | import java.io.PrintWriter; 21 | import java.io.StringWriter; 22 | import java.util.Arrays; 23 | import java.util.HashSet; 24 | import java.util.Locale; 25 | import java.util.Set; 26 | 27 | /** 28 | * Misc util methods 29 | */ 30 | public final class MiscUtil { 31 | private MiscUtil() { 32 | } 33 | 34 | public static String getStackTrace(Throwable t) { 35 | StringWriter sw = new StringWriter(); 36 | PrintWriter pw = new PrintWriter(sw); 37 | t.printStackTrace(pw); 38 | return sw.toString(); 39 | } 40 | 41 | public static String duration(long ms) { 42 | if (ms >= 1000) { 43 | return String.format(Locale.US, "%.2f sec", (double) ms / 1000); 44 | } 45 | return ms + " ms"; 46 | } 47 | 48 | public static T[] concat(T[] first, T[] second) { 49 | T[] result = Arrays.copyOf(first, first.length + second.length); 50 | System.arraycopy(second, 0, result, first.length, second.length); 51 | return result; 52 | } 53 | 54 | public static File createAndCheckFolder(String path, boolean dryRun) { 55 | File f = new File(path); 56 | 57 | if (dryRun) { 58 | return f; 59 | } 60 | 61 | if (!f.exists()) { 62 | f.mkdirs(); 63 | } 64 | 65 | if (!f.exists() || !f.isDirectory()) { 66 | throw new IllegalStateException("could not create folder: " + path); 67 | } 68 | return f; 69 | } 70 | 71 | public static String getFileExtensionLowerCase(File file) { 72 | return getFileExtension(file).toLowerCase(); 73 | } 74 | 75 | public static String getFileExtension(File file) { 76 | if (file == null) { 77 | return ""; 78 | } 79 | return file.getName().substring(file.getName().lastIndexOf(".") + 1); 80 | } 81 | 82 | public static String getFileNameWithoutExtension(File file) { 83 | String fileName = file.getName(); 84 | int pos = fileName.lastIndexOf("."); 85 | if (pos > 0) { 86 | fileName = fileName.substring(0, pos); 87 | } 88 | return fileName; 89 | } 90 | 91 | public static String getCmdProgressBar(float progress) { 92 | int loadingBarCount = 40; 93 | int bars = Math.round((float) loadingBarCount * progress); 94 | StringBuilder sb = new StringBuilder("\r["); 95 | 96 | for (int i = 0; i < loadingBarCount; i++) { 97 | if (i < bars) { 98 | sb.append("-"); 99 | } else { 100 | sb.append(" "); 101 | } 102 | } 103 | sb.append("] "); 104 | 105 | if (progress < 1f) { 106 | sb.append(String.format("%6s", String.format(Locale.US, "%.2f", progress * 100f))).append("%"); 107 | } else { 108 | sb.append("100.00%\n"); 109 | } 110 | 111 | return sb.toString(); 112 | } 113 | 114 | public static Set toSet(T elem) { 115 | Set set = new HashSet<>(1); 116 | set.add(elem); 117 | return set; 118 | } 119 | 120 | public static void deleteFolder(File folder) { 121 | if (folder != null && folder.exists()) { 122 | File[] files = folder.listFiles(); 123 | if (files != null) { //some JVMs return null for empty dirs 124 | for (File f : files) { 125 | if (f.isDirectory()) { 126 | deleteFolder(f); 127 | } else { 128 | f.delete(); 129 | } 130 | } 131 | } 132 | folder.delete(); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/AndroidConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | import at.favre.tools.dconvert.arg.ImageType; 21 | import at.favre.tools.dconvert.converters.descriptors.AndroidDensityDescriptor; 22 | import at.favre.tools.dconvert.util.MiscUtil; 23 | 24 | import java.awt.*; 25 | import java.io.File; 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | /** 30 | * Converts and creates Android-style resource set 31 | */ 32 | public class AndroidConverter extends APlatformConverter { 33 | 34 | private static final String ANDROID_FOLDER_NAME = "android"; 35 | 36 | @Override 37 | public List usedOutputDensities(Arguments arguments) { 38 | return getAndroidDensityDescriptors(arguments); 39 | } 40 | 41 | public static List getAndroidDensityDescriptors(Arguments arguments) { 42 | List list = new ArrayList<>(); 43 | String dirPrefix = arguments.createMipMapInsteadOfDrawableDir ? "mipmap" : "drawable"; 44 | if (arguments.includeAndroidLdpiTvdpi) { 45 | list.add(new AndroidDensityDescriptor(0.75f, "ldpi", dirPrefix + "-ldpi")); 46 | list.add(new AndroidDensityDescriptor(1.33f, "tvdpi", dirPrefix + "-tvdpi")); 47 | } 48 | list.add(new AndroidDensityDescriptor(1, "mdpi", dirPrefix + "-mdpi")); 49 | list.add(new AndroidDensityDescriptor(1.5f, "hdpi", dirPrefix + "-hdpi")); 50 | list.add(new AndroidDensityDescriptor(2, "xhdpi", dirPrefix + "-xhdpi")); 51 | list.add(new AndroidDensityDescriptor(3, "xxhdpi", dirPrefix + "-xxhdpi")); 52 | list.add(new AndroidDensityDescriptor(4, "xxxhdpi", dirPrefix + "-xxxhdpi")); 53 | return list; 54 | } 55 | 56 | @Override 57 | public String getConverterName() { 58 | return "android-converter"; 59 | } 60 | 61 | @Override 62 | public File createMainSubFolder(File destinationFolder, String targetImageFileName, Arguments arguments) { 63 | if (arguments.platform.size() > 1) { 64 | return MiscUtil.createAndCheckFolder(new File(destinationFolder, ANDROID_FOLDER_NAME).getAbsolutePath(), arguments.dryRun); 65 | } else { 66 | return destinationFolder; 67 | } 68 | } 69 | 70 | @Override 71 | public File createFolderForOutputFile(File mainSubFolder, AndroidDensityDescriptor density, Dimension dimension, String targetFileName, Arguments arguments) { 72 | return MiscUtil.createAndCheckFolder(new File(mainSubFolder, density.folderName).getAbsolutePath(), arguments.dryRun); 73 | } 74 | 75 | @Override 76 | public String createDestinationFileNameWithoutExtension(AndroidDensityDescriptor density, Dimension dimension, String targetFileName, Arguments arguments) { 77 | return targetFileName; 78 | } 79 | 80 | @Override 81 | public void onPreExecute(File dstFolder, String targetFileName, List densityDescriptions, ImageType imageType, Arguments arguments) throws Exception { 82 | //nothing 83 | } 84 | 85 | @Override 86 | public void onPostExecute(Arguments arguments) { 87 | //nothing 88 | } 89 | 90 | public static boolean isNinePatch(File file) { 91 | return file.getName().endsWith(".9.png"); 92 | } 93 | 94 | @Override 95 | public void clean(Arguments arguments) { 96 | if (arguments.platform.size() == 1) { 97 | for (AndroidDensityDescriptor androidDensityDescriptor : getAndroidDensityDescriptors(arguments)) { 98 | File dir = new File(arguments.dst, androidDensityDescriptor.folderName); 99 | MiscUtil.deleteFolder(dir); 100 | } 101 | } else { 102 | MiscUtil.deleteFolder(new File(arguments.dst, ANDROID_FOLDER_NAME)); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/ui/TaskbarProgress.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.ui; 18 | 19 | import org.apache.commons.lang3.SystemUtils; 20 | import org.bridj.Pointer; 21 | import org.bridj.cpp.com.COMRuntime; 22 | import org.bridj.cpp.com.shell.ITaskbarList3; 23 | 24 | import java.util.concurrent.ExecutorService; 25 | import java.util.concurrent.Executors; 26 | 27 | /** 28 | * Wrapper to use native windows taskbar features like progress, error etc. 29 | */ 30 | public interface TaskbarProgress { 31 | 32 | void updateProgress(double progress); 33 | 34 | void finish(); 35 | 36 | void error(); 37 | 38 | static TaskbarProgress create() { 39 | if (SystemUtils.IS_OS_WINDOWS) { 40 | return new WinTaskbarProgress(); 41 | } else { 42 | return new NoopTaskbarProgress(); 43 | } 44 | } 45 | 46 | /** 47 | * No-Op for non Windows OSes 48 | */ 49 | class NoopTaskbarProgress implements TaskbarProgress { 50 | 51 | @Override 52 | public void updateProgress(double progress) { 53 | } 54 | 55 | @Override 56 | public void finish() { 57 | } 58 | 59 | @Override 60 | public void error() { 61 | } 62 | } 63 | 64 | /** 65 | * Implementation for Windows OS 66 | */ 67 | final class WinTaskbarProgress implements TaskbarProgress { 68 | 69 | private ExecutorService executor; 70 | private static final long MAX = 10000; 71 | private Pointer hwnd; 72 | private ITaskbarList3 list; 73 | private boolean enabled = true; 74 | 75 | public WinTaskbarProgress() { 76 | try { 77 | hwnd = Pointer.pointerToAddress(com.sun.glass.ui.Window.getWindows().get(0).getNativeWindow()); 78 | } catch (Exception e) { 79 | enabled = false; 80 | return; 81 | } 82 | 83 | executor = Executors.newSingleThreadExecutor(r -> { 84 | Thread t = new Thread(r); 85 | t.setDaemon(true); 86 | return t; 87 | }); 88 | 89 | executor.execute(() -> { 90 | try { 91 | list = COMRuntime.newInstance(ITaskbarList3.class); 92 | } catch (ClassNotFoundException e) { 93 | e.printStackTrace(); 94 | } 95 | }); 96 | hwnd = Pointer.pointerToAddress(com.sun.glass.ui.Window.getWindows().get(0).getNativeWindow()); 97 | } 98 | 99 | public void updateProgress(double progress) { 100 | if (!enabled) return; 101 | executor.execute(() -> list.SetProgressValue((Pointer) hwnd, Math.round(progress * (double) MAX), MAX)); 102 | } 103 | 104 | public void finish() { 105 | if (!enabled) return; 106 | executor.execute(() -> { 107 | list.SetProgressValue((Pointer) hwnd, MAX, MAX); 108 | try { 109 | Thread.sleep(1000); 110 | } catch (InterruptedException e) { 111 | e.printStackTrace(); 112 | } 113 | list.SetProgressState((Pointer) hwnd, ITaskbarList3.TbpFlag.TBPF_NOPROGRESS); 114 | list.SetProgressValue((Pointer) hwnd, 0, MAX); 115 | list.Release(); 116 | }); 117 | } 118 | 119 | public void error() { 120 | if (!enabled) return; 121 | executor.execute(() -> { 122 | list.SetProgressState((Pointer) hwnd, ITaskbarList3.TbpFlag.TBPF_ERROR); 123 | list.SetProgressValue((Pointer) hwnd, MAX, MAX); 124 | try { 125 | Thread.sleep(1000); 126 | } catch (InterruptedException e) { 127 | e.printStackTrace(); 128 | } 129 | list.SetProgressState((Pointer) hwnd, ITaskbarList3.TbpFlag.TBPF_NOPROGRESS); 130 | list.SetProgressValue((Pointer) hwnd, 0, MAX); 131 | list.Release(); 132 | }); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/at/favre/tools/dconvert/test/DConvertTest.java: -------------------------------------------------------------------------------- 1 | package at.favre.tools.dconvert.test; 2 | 3 | import at.favre.tools.dconvert.DConvert; 4 | import at.favre.tools.dconvert.arg.Arguments; 5 | import at.favre.tools.dconvert.arg.EPlatform; 6 | import org.junit.Test; 7 | 8 | import java.io.File; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.concurrent.CountDownLatch; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | import static org.junit.Assert.assertTrue; 16 | 17 | /** 18 | * Tests main converter class 19 | */ 20 | public class DConvertTest extends AIntegrationTest { 21 | 22 | @Test 23 | public void testZeroFilesInput() throws Exception { 24 | TestCallback callback = new TestCallback(0, Collections.emptyList(), false, latch); 25 | new DConvert().execute(new Arguments.Builder(null, Arguments.DEFAULT_SCALE).threadCount(4).skipParamValidation(true).build(), false, callback); 26 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 27 | checkResult(callback); 28 | } 29 | 30 | @Test 31 | public void testSingleFileIosPlatformConverter() throws Exception { 32 | List files = AConverterTest.copyToTestPath(src, "png_example1_alpha_144.png"); 33 | Arguments arg = new Arguments.Builder(src, Arguments.DEFAULT_SCALE).platform(Collections.singleton(EPlatform.IOS)).dstFolder(dst).threadCount(4).build(); 34 | TestCallback callback = new TestCallback(files.size(), Collections.emptyList(), false, latch); 35 | new DConvert().execute(arg, false, callback); 36 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 37 | checkResult(callback); 38 | IOSConverterTest.checkOutDirIos(dst, arg, files); 39 | } 40 | 41 | @Test 42 | public void testAndroidPlatformConverter() throws Exception { 43 | List files = AConverterTest.copyToTestPath(src, "png_example3_alpha_128.png", "png_example1_alpha_144.png", "jpg_example2_512.jpg", "gif_example_640.gif", "png_example4_500.png", "psd_example_827.psd"); 44 | Arguments arg = new Arguments.Builder(src, Arguments.DEFAULT_SCALE).platform(Collections.singleton(EPlatform.ANDROID)).dstFolder(dst).threadCount(4).build(); 45 | TestCallback callback = new TestCallback(files.size(), Collections.emptyList(), false, latch); 46 | new DConvert().execute(arg, false, callback); 47 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 48 | checkResult(callback); 49 | AndroidConverterTest.checkOutDirAndroid(dst, arg, files); 50 | } 51 | 52 | @Test 53 | public void testAllPlatformConverter() throws Exception { 54 | List files = AConverterTest.copyToTestPath(src, "png_example3_alpha_128.png", "png_example1_alpha_144.png", "jpg_example2_512.jpg"); 55 | Arguments arg = new Arguments.Builder(src, Arguments.DEFAULT_SCALE).platform(EPlatform.getAll()).dstFolder(dst).threadCount(4).build(); 56 | TestCallback callback = new TestCallback(files.size() * EPlatform.getAll().size(), Collections.emptyList(), false, latch); 57 | new DConvert().execute(arg, false, callback); 58 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 59 | checkResult(callback); 60 | AConverterTest.checkMultiPlatformConvert(dst, arg, files); 61 | } 62 | 63 | private void checkResult(TestCallback callback) { 64 | assertEquals(callback.expectedJobs, callback.actualJobs); 65 | assertEquals(callback.expectedExceptions, callback.actualExceptions); 66 | assertEquals(callback.expectedHaltDuringProcess, callback.actualHaltDuringProcess); 67 | } 68 | 69 | private static class TestCallback implements DConvert.HandlerCallback { 70 | private final int expectedJobs; 71 | private final List expectedExceptions; 72 | private final boolean expectedHaltDuringProcess; 73 | private final CountDownLatch latch; 74 | private int actualJobs; 75 | private List actualExceptions; 76 | private boolean actualHaltDuringProcess; 77 | 78 | public TestCallback(int expectedJobs, List expectedExceptions, boolean expectedHaltDuringProcess, CountDownLatch latch) { 79 | this.expectedJobs = expectedJobs; 80 | this.expectedExceptions = expectedExceptions; 81 | this.expectedHaltDuringProcess = expectedHaltDuringProcess; 82 | this.latch = latch; 83 | } 84 | 85 | @Override 86 | public void onProgress(float percent) { 87 | } 88 | 89 | @Override 90 | public void onFinished(int finishedJobs, List exceptions, long time, boolean haltedDuringProcess, String log) { 91 | actualJobs = finishedJobs; 92 | actualExceptions = exceptions; 93 | actualHaltDuringProcess = haltedDuringProcess; 94 | latch.countDown(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/APlatformConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | import at.favre.tools.dconvert.arg.EScaleMode; 21 | import at.favre.tools.dconvert.arg.ImageType; 22 | import at.favre.tools.dconvert.converters.descriptors.DensityDescriptor; 23 | import at.favre.tools.dconvert.converters.scaling.ImageHandler; 24 | import at.favre.tools.dconvert.util.DensityBucketUtil; 25 | import at.favre.tools.dconvert.util.ImageUtil; 26 | import at.favre.tools.dconvert.util.LoadedImage; 27 | import at.favre.tools.dconvert.util.MiscUtil; 28 | 29 | import java.awt.*; 30 | import java.io.File; 31 | import java.util.*; 32 | import java.util.List; 33 | 34 | /** 35 | * The main logic of all platform converters 36 | */ 37 | public abstract class APlatformConverter implements IPlatformConverter { 38 | 39 | @Override 40 | public Result convert(File srcImage, Arguments args) { 41 | try { 42 | File destinationFolder = args.dst; 43 | LoadedImage imageData = ImageUtil.loadImage(srcImage); 44 | String targetImageFileName = MiscUtil.getFileNameWithoutExtension(srcImage); 45 | ImageType imageType = Arguments.getImageType(srcImage); 46 | boolean isNinePatch = AndroidConverter.isNinePatch(srcImage) && getClass() == AndroidConverter.class; 47 | 48 | StringBuilder log = new StringBuilder(); 49 | log.append(getConverterName()).append(": ").append(targetImageFileName).append(" ") 50 | .append(imageData.getImage().getWidth()).append("x").append(imageData.getImage().getHeight()).append(" (").append(args.scale).append(args.scaleMode == EScaleMode.FACTOR ? "x" : "dp").append(")\n"); 51 | 52 | Map densityMap = DensityBucketUtil.getDensityBuckets(usedOutputDensities(args), new Dimension(imageData.getImage().getWidth(), imageData.getImage().getHeight()), args, args.scale, isNinePatch); 53 | 54 | File mainSubFolder = createMainSubFolder(destinationFolder, targetImageFileName, args); 55 | 56 | onPreExecute(mainSubFolder, targetImageFileName, usedOutputDensities(args), imageType, args); 57 | 58 | List allResultingFiles = new ArrayList<>(); 59 | 60 | for (Map.Entry entry : densityMap.entrySet()) { 61 | File dstFolder = createFolderForOutputFile(mainSubFolder, entry.getKey(), entry.getValue(), targetImageFileName, args); 62 | 63 | if ((dstFolder.isDirectory() && dstFolder.exists()) || args.dryRun) { 64 | File imageFile = new File(dstFolder, createDestinationFileNameWithoutExtension(entry.getKey(), entry.getValue(), targetImageFileName, args)); 65 | 66 | log.append("process ").append(imageFile).append(" with ").append(entry.getValue().width).append("x").append(entry.getValue().height).append(" (x") 67 | .append(entry.getKey().scale).append(") ").append(isNinePatch ? "(9-patch)" : "").append("\n"); 68 | 69 | if (!args.dryRun) { 70 | List files = new ImageHandler(args).saveToFile(imageFile, imageData, entry.getValue(), isNinePatch); 71 | 72 | allResultingFiles.addAll(files); 73 | 74 | for (File file : files) { 75 | log.append("compressed to disk: ").append(file).append(" (").append(String.format(Locale.US, "%.2f", (float) file.length() / 1024f)).append("kB)\n"); 76 | } 77 | 78 | if (files.isEmpty()) { 79 | log.append("files skipped\n"); 80 | } 81 | } 82 | } else { 83 | throw new IllegalStateException("could not create " + dstFolder); 84 | } 85 | } 86 | 87 | onPostExecute(args); 88 | 89 | imageData.getImage().flush(); 90 | 91 | return new Result(log.toString(), allResultingFiles); 92 | } catch (Exception e) { 93 | e.printStackTrace(); 94 | return new Result(null, e, Collections.emptyList()); 95 | } 96 | } 97 | 98 | public abstract List usedOutputDensities(Arguments arguments); 99 | 100 | public abstract String getConverterName(); 101 | 102 | public abstract File createMainSubFolder(File destinationFolder, String targetImageFileName, Arguments arguments); 103 | 104 | public abstract File createFolderForOutputFile(File mainSubFolder, T density, Dimension dimension, String targetFileName, Arguments arguments); 105 | 106 | public abstract String createDestinationFileNameWithoutExtension(T density, Dimension dimension, String targetFileName, Arguments arguments); 107 | 108 | public abstract void onPreExecute(File dstFolder, String targetFileName, List densityDescriptions, ImageType imageType, Arguments arguments) throws Exception; 109 | 110 | public abstract void onPostExecute(Arguments arguments); 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/WorkerHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | import at.favre.tools.dconvert.converters.IPlatformConverter; 21 | import at.favre.tools.dconvert.converters.Result; 22 | import at.favre.tools.dconvert.converters.postprocessing.IPostProcessor; 23 | 24 | import java.io.File; 25 | import java.util.ArrayList; 26 | import java.util.Collections; 27 | import java.util.List; 28 | import java.util.concurrent.ArrayBlockingQueue; 29 | import java.util.concurrent.ExecutorService; 30 | import java.util.concurrent.ThreadPoolExecutor; 31 | import java.util.concurrent.TimeUnit; 32 | 33 | /** 34 | * Handles post processing tasks 35 | */ 36 | public class WorkerHandler { 37 | 38 | private final List processors; 39 | private final ExecutorService threadPool; 40 | private final Arguments arguments; 41 | private final Callback callback; 42 | private int jobCount; 43 | 44 | public WorkerHandler(List processors, Arguments arguments, Callback callback) { 45 | this.processors = processors; 46 | this.threadPool = new ThreadPoolExecutor(arguments.threadCount, arguments.threadCount, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1024 * 10)); 47 | this.callback = callback; 48 | this.arguments = arguments; 49 | } 50 | 51 | public void start(List allFiles) { 52 | this.jobCount = allFiles.size() * processors.size(); 53 | 54 | InternalCallback internalCallback = new InternalCallback(callback); 55 | 56 | for (T processor : processors) { 57 | for (File fileToProcess : allFiles) { 58 | threadPool.execute(new Worker(fileToProcess, processor, arguments, internalCallback)); 59 | } 60 | } 61 | 62 | threadPool.shutdown(); 63 | 64 | if (jobCount == 0) { 65 | callback.onFinished(0, Collections.emptyList(), new StringBuilder(), Collections.emptyList(), false); 66 | } 67 | } 68 | 69 | private class Worker implements Runnable { 70 | private File unprocessedFile; 71 | private T processor; 72 | private InternalCallback callback; 73 | private final Arguments arguments; 74 | 75 | public Worker(File unprocessedFile, T processors, Arguments arguments, InternalCallback callback) { 76 | this.unprocessedFile = unprocessedFile; 77 | this.arguments = arguments; 78 | this.processor = processors; 79 | this.callback = callback; 80 | } 81 | 82 | @Override 83 | public void run() { 84 | Result result = null; 85 | if (IPostProcessor.class.isInstance(processor)) { 86 | result = ((IPostProcessor) processor).process(unprocessedFile, arguments.keepUnoptimizedFilesPostProcessor); 87 | } else if (IPlatformConverter.class.isInstance(processor)) { 88 | result = ((IPlatformConverter) processor).convert(unprocessedFile, arguments); 89 | } 90 | callback.onJobFinished(result); 91 | } 92 | } 93 | 94 | private class InternalCallback { 95 | private int currentJobCount = 0; 96 | private List exceptionList = new ArrayList<>(); 97 | private Callback callback; 98 | private StringBuilder logBuilder = new StringBuilder(); 99 | private boolean canceled = false; 100 | private List files = new ArrayList<>(); 101 | 102 | public InternalCallback(Callback callback) { 103 | this.callback = callback; 104 | } 105 | 106 | synchronized void onJobFinished(Result result) { 107 | if (!canceled) { 108 | currentJobCount++; 109 | 110 | if (result != null) { 111 | if (result.log != null && result.log.length() > 0) { 112 | logBuilder.append(result.log).append("\n"); 113 | } 114 | if (result.processedFiles != null) { 115 | files.addAll(result.processedFiles); 116 | } 117 | if (result.exception != null) { 118 | exceptionList.add(result.exception); 119 | 120 | if (arguments.haltOnError) { 121 | canceled = true; 122 | threadPool.shutdownNow(); 123 | callback.onFinished(currentJobCount, files, logBuilder, exceptionList, true); 124 | } 125 | } 126 | } 127 | 128 | if (!canceled) { 129 | if (currentJobCount == jobCount) { 130 | callback.onFinished(currentJobCount, files, logBuilder, exceptionList, false); 131 | } else { 132 | callback.onProgress((float) currentJobCount / (float) jobCount); 133 | } 134 | } 135 | } 136 | } 137 | } 138 | 139 | public interface Callback { 140 | void onProgress(float percent); 141 | 142 | void onFinished(int finishedJobs, List outFiles, StringBuilder log, List exceptions, boolean haltedDuringProcess); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/util/DensityBucketUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.util; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | import at.favre.tools.dconvert.arg.EScaleMode; 21 | import at.favre.tools.dconvert.converters.descriptors.DensityDescriptor; 22 | 23 | import java.awt.*; 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.util.Map; 27 | import java.util.TreeMap; 28 | 29 | /** 30 | * Helps assembling needed densities to convert to 31 | */ 32 | public final class DensityBucketUtil { 33 | private static final float SVG_UPSCALE_FACTOR = 4; 34 | 35 | private DensityBucketUtil() { 36 | } 37 | 38 | public static Map getDensityBuckets(java.util.List densities, Dimension srcDimension, Arguments args, float scale, boolean isNinePatch) throws IOException { 39 | 40 | if (isNinePatch) { 41 | srcDimension.setSize(srcDimension.getWidth() - 2, srcDimension.getHeight() - 2); 42 | } 43 | 44 | switch (args.scaleMode) { 45 | case DP_WIDTH: 46 | return getDensityBucketsWithDpScale(densities, srcDimension, args, scale); 47 | case DP_HEIGHT: 48 | return getDensityBucketsHeightDpScale(densities, srcDimension, args, scale); 49 | default: 50 | case FACTOR: 51 | return getDensityBucketsWithFactorScale(densities, srcDimension, args, scale); 52 | } 53 | } 54 | 55 | private static Map getDensityBucketsWithDpScale(java.util.List densities, Dimension srcDimension, Arguments args, float scale) throws IOException { 56 | float scaleFactor = scale / (float) srcDimension.width; 57 | 58 | int baseWidth = (int) args.round(scale); 59 | int baseHeight = (int) args.round(scaleFactor * (float) srcDimension.height); 60 | 61 | Map bucketMap = new TreeMap<>(); 62 | densities.stream().filter(density -> (int) args.round(baseWidth * density.scale) <= srcDimension.width || !args.skipUpscaling).forEach(density -> { 63 | bucketMap.put(density, new Dimension((int) args.round(baseWidth * density.scale), 64 | (int) args.round(baseHeight * density.scale))); 65 | }); 66 | return bucketMap; 67 | } 68 | 69 | private static Map getDensityBucketsHeightDpScale(java.util.List densities, Dimension srcDimension, Arguments args, float scale) throws IOException { 70 | float scaleFactor = scale / (float) srcDimension.height; 71 | 72 | int baseWidth = (int) args.round(scaleFactor * (float) srcDimension.width); 73 | int baseHeight = (int) args.round(scale); 74 | 75 | Map bucketMap = new TreeMap<>(); 76 | densities.stream().filter(density -> (int) args.round(baseHeight * density.scale) <= srcDimension.height || !args.skipUpscaling).forEach(density -> { 77 | bucketMap.put(density, new Dimension((int) args.round(baseWidth * density.scale), 78 | (int) args.round(baseHeight * density.scale))); 79 | }); 80 | return bucketMap; 81 | } 82 | 83 | private static Map getDensityBucketsWithFactorScale(java.util.List densities, Dimension srcDimension, Arguments args, float scale) { 84 | double baseWidth = (double) srcDimension.width / scale; 85 | double baseHeight = (double) srcDimension.height / scale; 86 | 87 | Map bucketMap = new TreeMap<>(); 88 | densities.stream().filter(density -> scale >= density.scale || !args.skipUpscaling).forEach(density -> { 89 | bucketMap.put(density, new Dimension((int) args.round(baseWidth * density.scale), 90 | (int) args.round(baseHeight * density.scale))); 91 | }); 92 | return bucketMap; 93 | } 94 | 95 | private static Dimension getHqDimension(File image, Arguments args) throws IOException { 96 | Dimension srcDimension = ImageUtil.getImageDimension(image); 97 | Dimension hqDimension; 98 | if (args.scaleMode == EScaleMode.FACTOR && args.scale < SVG_UPSCALE_FACTOR) { 99 | hqDimension = new Dimension((int) args.round(SVG_UPSCALE_FACTOR / args.scale * (float) srcDimension.width), (int) args.round(SVG_UPSCALE_FACTOR / args.scale * (float) srcDimension.width)); 100 | } else if (args.scaleMode == EScaleMode.DP_WIDTH && (args.scale * SVG_UPSCALE_FACTOR < srcDimension.width)) { 101 | float scaleFactor = args.scale / (float) srcDimension.width * SVG_UPSCALE_FACTOR; 102 | hqDimension = new Dimension((int) args.round(scaleFactor * (float) srcDimension.width), (int) args.round(scaleFactor * (float) srcDimension.height)); 103 | } else if (args.scaleMode == EScaleMode.DP_HEIGHT && (args.scale * SVG_UPSCALE_FACTOR < srcDimension.height)) { 104 | float scaleFactor = args.scale / (float) srcDimension.height * SVG_UPSCALE_FACTOR; 105 | hqDimension = new Dimension((int) args.round(scaleFactor * (float) srcDimension.width), (int) args.round(scaleFactor * (float) srcDimension.height)); 106 | } else { 107 | hqDimension = srcDimension; 108 | } 109 | return hqDimension; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/IOSConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | import at.favre.tools.dconvert.arg.ImageType; 21 | import at.favre.tools.dconvert.converters.descriptors.PostfixDescriptor; 22 | import at.favre.tools.dconvert.util.MiscUtil; 23 | 24 | import java.awt.*; 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.io.PrintWriter; 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | /** 32 | * Needed info to convert for Android 33 | */ 34 | public class IOSConverter extends APlatformConverter { 35 | public static final String ROOT_FOLDER = "AssetCatalog"; 36 | private static final String IOS_FOLDER_NAME = "ios"; 37 | 38 | @Override 39 | public List usedOutputDensities(Arguments arguments) { 40 | return getIosDescriptors(); 41 | } 42 | 43 | public static List getIosDescriptors() { 44 | List list = new ArrayList<>(); 45 | list.add(new PostfixDescriptor(1, "1x", "")); 46 | list.add(new PostfixDescriptor(2, "2x", "@2x")); 47 | list.add(new PostfixDescriptor(3, "3x", "@3x")); 48 | return list; 49 | } 50 | 51 | @Override 52 | public String getConverterName() { 53 | return "ios-converter"; 54 | } 55 | 56 | @Override 57 | public File createMainSubFolder(File destinationFolder, String targetImageFileName, Arguments arguments) { 58 | if (arguments.platform.size() > 1) { 59 | destinationFolder = MiscUtil.createAndCheckFolder(new File(destinationFolder, IOS_FOLDER_NAME).getAbsolutePath(), arguments.dryRun); 60 | } 61 | if (arguments.iosCreateImagesetFolders) { 62 | return MiscUtil.createAndCheckFolder(new File(destinationFolder, targetImageFileName + ".imageset").getAbsolutePath(), arguments.dryRun); 63 | } else { 64 | return MiscUtil.createAndCheckFolder(new File(destinationFolder, ROOT_FOLDER).getAbsolutePath(), arguments.dryRun); 65 | } 66 | } 67 | 68 | @Override 69 | public File createFolderForOutputFile(File mainSubFolder, PostfixDescriptor density, Dimension dimension, String targetFileName, Arguments arguments) { 70 | return mainSubFolder; 71 | } 72 | 73 | @Override 74 | public String createDestinationFileNameWithoutExtension(PostfixDescriptor density, Dimension dimension, String targetFileName, Arguments arguments) { 75 | return targetFileName + density.postFix; 76 | } 77 | 78 | @Override 79 | public void onPreExecute(File dstFolder, String targetFileName, List densityDescriptions, ImageType imageType, Arguments arguments) throws Exception { 80 | if (!arguments.dryRun && arguments.iosCreateImagesetFolders) { 81 | writeContentsJson(dstFolder, targetFileName, densityDescriptions, Arguments.getOutCompressionForType(arguments.outputCompressionMode, imageType)); 82 | } 83 | } 84 | 85 | @Override 86 | public void onPostExecute(Arguments arguments) { 87 | 88 | } 89 | 90 | private void writeContentsJson(File dstFolder, String targetFileName, List iosDensityDescriptions, List compressions) throws IOException { 91 | File contentJson = new File(dstFolder, "Contents.json"); 92 | 93 | if (contentJson.exists()) { 94 | contentJson.delete(); 95 | } 96 | contentJson.createNewFile(); 97 | 98 | try (PrintWriter out = new PrintWriter(contentJson)) { 99 | out.println(createContentsJson(targetFileName, iosDensityDescriptions, compressions)); 100 | } 101 | } 102 | 103 | private String createContentsJson(String targetFileName, List iosDensityDescriptions, List compressions) { 104 | StringBuilder sb = new StringBuilder("{\n\t\"images\": ["); 105 | for (ImageType.ECompression compression : compressions) { 106 | for (PostfixDescriptor densityDescription : iosDensityDescriptions) { 107 | sb.append("\n\t\t{\n" + 108 | "\t\t\t\"filename\": \"" + targetFileName + densityDescription.postFix + "." + compression.name().toLowerCase() + "\",\n" + 109 | "\t\t\t\"idiom\": \"universal\",\n" + 110 | "\t\t\t\"scale\": \"" + densityDescription.name + "\"\n" + 111 | "\t\t},"); 112 | } 113 | } 114 | sb.setLength(sb.length() - 1); 115 | sb.append("\n\t],\n\t\"info\": {\n\t\t\"author\": \"xcode\",\n\t\t\"version\": 1\n\t}\n}"); 116 | 117 | return sb.toString(); 118 | } 119 | 120 | @Override 121 | public void clean(Arguments arguments) { 122 | if (arguments.platform.size() == 1) { 123 | if (arguments.iosCreateImagesetFolders) { 124 | for (File filesToProcess : arguments.filesToProcess) { 125 | MiscUtil.deleteFolder(new File(arguments.dst, MiscUtil.getFileNameWithoutExtension(filesToProcess) + ".imageset")); 126 | } 127 | } else { 128 | MiscUtil.deleteFolder(new File(arguments.dst, ROOT_FOLDER)); 129 | } 130 | } else { 131 | MiscUtil.deleteFolder(new File(arguments.dst, IOS_FOLDER_NAME)); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/test/java/at/favre/tools/dconvert/test/IOSConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package at.favre.tools.dconvert.test; 19 | 20 | import at.favre.tools.dconvert.arg.Arguments; 21 | import at.favre.tools.dconvert.arg.EOutputCompressionMode; 22 | import at.favre.tools.dconvert.arg.EPlatform; 23 | import at.favre.tools.dconvert.converters.IOSConverter; 24 | import at.favre.tools.dconvert.converters.descriptors.PostfixDescriptor; 25 | import at.favre.tools.dconvert.util.ImageUtil; 26 | import at.favre.tools.dconvert.util.MiscUtil; 27 | import org.junit.Test; 28 | 29 | import java.awt.*; 30 | import java.io.File; 31 | import java.io.IOException; 32 | import java.util.ArrayList; 33 | import java.util.Collections; 34 | import java.util.List; 35 | import java.util.Map; 36 | import java.util.stream.Collectors; 37 | 38 | import static org.junit.Assert.assertEquals; 39 | import static org.junit.Assert.assertTrue; 40 | 41 | /** 42 | * Unit test of the {@link at.favre.tools.dconvert.converters.IPlatformConverter} for ios 43 | */ 44 | public class IOSConverterTest extends AConverterTest { 45 | @Override 46 | protected EPlatform getType() { 47 | return EPlatform.IOS; 48 | } 49 | 50 | @Override 51 | protected void checkOutDir(File dstDir, Arguments arguments, List files, EPlatform type) throws IOException { 52 | checkOutDirIos(dstDir, arguments, files); 53 | } 54 | 55 | @Test 56 | public void testMultiplePngImagesetFolders() throws Exception { 57 | List files = copyToTestPath(defaultSrc, "png_example1_alpha_144.png", "png_example2_alpha_144.png", "jpg_example2_512.jpg"); 58 | test(new Arguments.Builder(defaultSrc, DEFAULT_SCALE).compression(EOutputCompressionMode.SAME_AS_INPUT_PREF_PNG, 0.5f) 59 | .dstFolder(defaultDst).platform(Collections.singleton(getType())).iosCreateImagesetFolders(true).build(), files); 60 | } 61 | 62 | @Test 63 | public void testSinglePngImagesetFolder() throws Exception { 64 | List files = copyToTestPath(defaultSrc, "png_example1_alpha_144.png"); 65 | test(new Arguments.Builder(defaultSrc, DEFAULT_SCALE).compression(EOutputCompressionMode.SAME_AS_INPUT_PREF_PNG, 0.5f) 66 | .dstFolder(defaultDst).platform(Collections.singleton(getType())).iosCreateImagesetFolders(true).build(), files); 67 | } 68 | 69 | public static void checkOutDirIos(File dstDir, Arguments arguments, List files) throws IOException { 70 | Map dimensionMap = createDimensionMap(files); 71 | 72 | List densityDescriptors = IOSConverter.getIosDescriptors(); 73 | 74 | System.out.println("ios-convert " + files); 75 | 76 | if (arguments.iosCreateImagesetFolders) { 77 | checkWithImagesetFolders(dstDir, arguments, files, dimensionMap, densityDescriptors); 78 | } else { 79 | checkOutDirPostfixDescr(new File(dstDir, IOSConverter.ROOT_FOLDER), arguments, files, densityDescriptors); 80 | } 81 | } 82 | 83 | private static void checkWithImagesetFolders(File dstDir, Arguments arguments, List files, Map dimensionMap, List densityDescriptors) throws IOException { 84 | assertTrue("src files and dst folder count should match", files.size() == dstDir.listFiles().length); 85 | for (File iosImgFolder : dstDir.listFiles()) { 86 | boolean found = false; 87 | File srcFile = null; 88 | for (File file : files) { 89 | if (String.valueOf(MiscUtil.getFileNameWithoutExtension(file) + ".imageset").equals(iosImgFolder.getName())) { 90 | found = true; 91 | srcFile = file; 92 | break; 93 | } 94 | } 95 | 96 | assertTrue("root image folder should be found ", found); 97 | assertTrue("image folder should contain at least 1 file", iosImgFolder.listFiles().length > 0); 98 | 99 | List expectedFiles = new ArrayList<>(); 100 | for (PostfixDescriptor densityDescriptor : densityDescriptors) { 101 | final File finalSrcFile = srcFile; 102 | expectedFiles.addAll(Arguments.getOutCompressionForType( 103 | arguments.outputCompressionMode, Arguments.getImageType(srcFile)).stream().map(compression -> 104 | new ImageInfo(finalSrcFile, MiscUtil.getFileNameWithoutExtension(finalSrcFile) + densityDescriptor.postFix + "." + compression.extension, densityDescriptor.scale)).collect(Collectors.toList())); 105 | } 106 | 107 | for (File dstImageFile : iosImgFolder.listFiles()) { 108 | for (ImageInfo expectedFile : expectedFiles) { 109 | if (dstImageFile.getName().equals(expectedFile.targetFileName)) { 110 | expectedFile.found = true; 111 | 112 | Dimension expectedDimension = getScaledDimension(expectedFile.srcFile, arguments, dimensionMap.get(expectedFile.srcFile), expectedFile.scale, false); 113 | assertEquals("dimensions should match", expectedDimension, ImageUtil.getImageDimension(dstImageFile)); 114 | } 115 | } 116 | } 117 | 118 | for (ImageInfo expectedFile : expectedFiles) { 119 | assertTrue(expectedFile.targetFileName + " expected in folder " + srcFile, expectedFile.found); 120 | } 121 | 122 | System.out.print("found " + expectedFiles.size() + " files in " + iosImgFolder + ", "); 123 | } 124 | System.out.println(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/arg/EScalingAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.arg; 18 | 19 | import at.favre.tools.dconvert.converters.scaling.NaiveGraphics2dAlgorithm; 20 | import at.favre.tools.dconvert.converters.scaling.ProgressiveAlgorithm; 21 | import at.favre.tools.dconvert.converters.scaling.ResampleAlgorithm; 22 | import at.favre.tools.dconvert.converters.scaling.ScaleAlgorithm; 23 | import com.mortennobel.imagescaling.ResampleFilters; 24 | 25 | import java.awt.*; 26 | import java.util.*; 27 | import java.util.List; 28 | import java.util.stream.Collectors; 29 | 30 | /** 31 | * Supported scaling algorithms in dconvert 32 | */ 33 | public enum EScalingAlgorithm { 34 | 35 | LANCZOS1(new ResampleAlgorithm(new ResampleAlgorithm.LanczosFilter(1)), "lanczos1", new Type[]{Type.DOWNSCALING, Type.UPSCALING}, false), 36 | LANCZOS2(new ResampleAlgorithm(new ResampleAlgorithm.LanczosFilter(2)), "lanczos2", new Type[]{Type.DOWNSCALING, Type.UPSCALING}, true), 37 | LANCZOS3(new ResampleAlgorithm(new ResampleAlgorithm.LanczosFilter(3)), "lanczos3", new Type[]{Type.DOWNSCALING, Type.UPSCALING}, true), 38 | LANCZOS4(new ResampleAlgorithm(new ResampleAlgorithm.LanczosFilter(4)), "lanczos4", new Type[]{Type.DOWNSCALING, Type.UPSCALING}, false), 39 | LANCZOS5(new ResampleAlgorithm(new ResampleAlgorithm.LanczosFilter(5)), "lanczos5", new Type[]{Type.DOWNSCALING, Type.UPSCALING}, false), 40 | MITCHELL(new ResampleAlgorithm(ResampleFilters.getMitchellFilter()), "mitchell", new Type[]{Type.DOWNSCALING, Type.UPSCALING}, true), 41 | BSPLINE(new ResampleAlgorithm(ResampleFilters.getBSplineFilter()), "bspline", new Type[]{Type.DOWNSCALING, Type.UPSCALING}, false), 42 | HERMITE(new ResampleAlgorithm(ResampleFilters.getHermiteFilter()), "hermite", new Type[]{Type.DOWNSCALING, Type.UPSCALING}, false), 43 | NEAREST_NEIGHBOR(new NaiveGraphics2dAlgorithm(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR), "nearestNeighbor", new Type[]{Type.DOWNSCALING, Type.UPSCALING}, true), 44 | BILINEAR_PROGRESSIVE(new ProgressiveAlgorithm(ProgressiveAlgorithm.Type.NOBEL_BILINEAR), "bilinearProgressive", new Type[]{Type.DOWNSCALING}, true), 45 | BICUBIC_PROGRESSIVE(new ProgressiveAlgorithm(ProgressiveAlgorithm.Type.NOBEL_BICUBUC), "bicubicProgressive", new Type[]{Type.DOWNSCALING}, true), 46 | NEAREST_NEIGHBOR_PROGRESSIVE(new ProgressiveAlgorithm(ProgressiveAlgorithm.Type.NOBEL_NEAREST_NEIGHBOR), "nearestNeighborProgressive", new Type[]{Type.DOWNSCALING}, false), 47 | BILINEAR_PROGRESSIVE2(new ProgressiveAlgorithm(ProgressiveAlgorithm.Type.THUMBNAILATOR_BILINEAR), "bilinearProgressive2", new Type[]{Type.DOWNSCALING}, false), 48 | BICUBIC_PROGRESSIVE_SMOOTH(new ProgressiveAlgorithm(ProgressiveAlgorithm.Type.IMGSCALR_SEVENTH_STEP), "bicubicProgressiveSmooth", new Type[]{Type.DOWNSCALING}, false), 49 | BILINEAR_LANCZOS2(new ProgressiveAlgorithm(ProgressiveAlgorithm.Type.PROGRESSIVE_BILINEAR_AND_LANCZOS3), "bilinearLanczos2", new Type[]{Type.DOWNSCALING}, true), 50 | BILINEAR_LANCZOS3(new ProgressiveAlgorithm(ProgressiveAlgorithm.Type.PROGRESSIVE_BILINEAR_AND_LANCZOS3), "bilinearLanczos3", new Type[]{Type.DOWNSCALING}, false), 51 | BICUBIC(new NaiveGraphics2dAlgorithm(RenderingHints.VALUE_INTERPOLATION_BICUBIC), "bicubic", new Type[]{Type.UPSCALING}, true), 52 | BILINEAR(new NaiveGraphics2dAlgorithm(RenderingHints.VALUE_INTERPOLATION_BILINEAR), "bilinear", new Type[]{Type.UPSCALING}, true); 53 | 54 | public enum Type { 55 | UPSCALING, DOWNSCALING 56 | } 57 | 58 | private final ScaleAlgorithm algorithm; 59 | private final String cliName; 60 | private final List supportedForType; 61 | private final boolean enabled; 62 | 63 | EScalingAlgorithm(ScaleAlgorithm algorithm, String cliName, Type[] supportedForType, boolean enabled) { 64 | this.algorithm = algorithm; 65 | this.cliName = cliName; 66 | this.supportedForType = Collections.unmodifiableList(Arrays.asList(supportedForType)); 67 | this.enabled = enabled; 68 | } 69 | 70 | public ScaleAlgorithm getImplementation() { 71 | return algorithm; 72 | } 73 | 74 | public String getName() { 75 | return cliName; 76 | } 77 | 78 | public List getSupportedForType() { 79 | return supportedForType; 80 | } 81 | 82 | public boolean isEnabled() { 83 | return enabled; 84 | } 85 | 86 | public static EScalingAlgorithm getByName(String name) { 87 | for (EScalingAlgorithm eScalingAlgorithm : getAllEnabled()) { 88 | if (eScalingAlgorithm.getName().equals(name)) { 89 | return eScalingAlgorithm; 90 | } 91 | } 92 | return null; 93 | } 94 | 95 | public static Set getForType(Type type) { 96 | return getAllEnabled().stream().filter(eScalingAlgorithm -> eScalingAlgorithm.getSupportedForType().contains(type)).collect(Collectors.toSet()); 97 | } 98 | 99 | public static String getCliArgString(Type type) { 100 | StringBuilder sb = new StringBuilder(); 101 | getAllEnabled().stream().filter(eScalingAlgorithm -> eScalingAlgorithm.getSupportedForType().contains(type)).forEach(eScalingAlgorithm -> { 102 | sb.append(eScalingAlgorithm.getName()).append("|"); 103 | }); 104 | String argList = sb.toString(); 105 | return argList.substring(0, argList.length() - 1); 106 | } 107 | 108 | public static Set getAllEnabled() { 109 | Set set = new HashSet<>(); 110 | for (EScalingAlgorithm eScalingAlgorithm : EScalingAlgorithm.values()) { 111 | if (eScalingAlgorithm.isEnabled()) { 112 | set.add(eScalingAlgorithm); 113 | } 114 | } 115 | return set; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/scaling/ProgressiveAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.scaling; 18 | 19 | import com.mortennobel.imagescaling.*; 20 | import org.imgscalr.Scalr; 21 | 22 | import java.awt.*; 23 | import java.awt.image.BufferedImage; 24 | 25 | /** 26 | * A best of of progressive scaling algorithms from different libs 27 | */ 28 | public class ProgressiveAlgorithm implements ScaleAlgorithm { 29 | public enum Type { 30 | /** 31 | * Algorithms from https://github.com/mortennobel/java-image-scaling 32 | */ 33 | NOBEL_BILINEAR, NOBEL_BICUBUC, NOBEL_NEAREST_NEIGHBOR, NOBEL_LANCZOS3, 34 | /** 35 | * Algorithms from https://github.com/coobird/thumbnailator 36 | */ 37 | THUMBNAILATOR_BILINEAR, THUMBNAILATOR_BICUBUC, 38 | /** 39 | * Algorithms from https://github.com/thebuzzmedia/imgscalr 40 | */ 41 | IMGSCALR_SEVENTH_STEP, IMGSCALR_HALF_STEP, 42 | /** 43 | * Combination of bilinear with lanczos3, uses bilinear if target is at least half of src 44 | */ 45 | PROGRESSIVE_BILINEAR_AND_LANCZOS2, PROGRESSIVE_BILINEAR_AND_LANCZOS3 46 | } 47 | 48 | public final Type type; 49 | 50 | public ProgressiveAlgorithm(Type type) { 51 | this.type = type; 52 | } 53 | 54 | @Override 55 | public BufferedImage scale(BufferedImage imageToScale, int dWidth, int dHeight) { 56 | switch (type) { 57 | case NOBEL_BILINEAR: 58 | return new MultiStepRescaleOp(dWidth, dHeight, RenderingHints.VALUE_INTERPOLATION_BILINEAR) 59 | .filter(imageToScale, null); 60 | case NOBEL_BICUBUC: 61 | return new MultiStepRescaleOp(dWidth, dHeight, RenderingHints.VALUE_INTERPOLATION_BICUBIC) 62 | .filter(imageToScale, null); 63 | case NOBEL_NEAREST_NEIGHBOR: 64 | return new MultiStepRescaleOp(dWidth, dHeight, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR) 65 | .filter(imageToScale, null); 66 | case NOBEL_LANCZOS3: 67 | return new MultiStepLanczos3RescaleOp(dWidth, dHeight).filter(imageToScale, null); 68 | case PROGRESSIVE_BILINEAR_AND_LANCZOS2: 69 | return scaleProgressiveLanczos(imageToScale, dWidth, dHeight, 2); 70 | case PROGRESSIVE_BILINEAR_AND_LANCZOS3: 71 | return scaleProgressiveLanczos(imageToScale, dWidth, dHeight, 3); 72 | case THUMBNAILATOR_BILINEAR: 73 | return new ThumbnailnatorProgressiveAlgorithm(RenderingHints.VALUE_INTERPOLATION_BILINEAR).scale(imageToScale, dWidth, dHeight); 74 | case THUMBNAILATOR_BICUBUC: 75 | return new ThumbnailnatorProgressiveAlgorithm(RenderingHints.VALUE_INTERPOLATION_BICUBIC).scale(imageToScale, dWidth, dHeight); 76 | case IMGSCALR_SEVENTH_STEP: 77 | return Scalr.resize(imageToScale, Scalr.Method.ULTRA_QUALITY, Scalr.Mode.FIT_EXACT, dWidth, dHeight, null); 78 | case IMGSCALR_HALF_STEP: 79 | return Scalr.resize(imageToScale, Scalr.Method.QUALITY, Scalr.Mode.FIT_EXACT, dWidth, dHeight, null); 80 | default: 81 | throw new IllegalArgumentException("unknown algorithm"); 82 | } 83 | } 84 | 85 | private BufferedImage scaleProgressiveLanczos(BufferedImage imageToScale, int dstWidth, int dstHeight, float radius) { 86 | if (dstWidth < (imageToScale.getWidth() / 2) && dstHeight < (imageToScale.getHeight() / 2)) { 87 | return new ThumbnailnatorProgressiveAlgorithm(RenderingHints.VALUE_INTERPOLATION_BILINEAR).scale(imageToScale, dstWidth, dstHeight); 88 | } else { 89 | return new ResampleAlgorithm(new ResampleAlgorithm.LanczosFilter(radius)).scale(imageToScale, dstWidth, dstHeight); 90 | } 91 | } 92 | 93 | private final class MultiStepLanczos3RescaleOp extends AdvancedResizeOp { 94 | private MultiStepLanczos3RescaleOp(int dstWidth, int dstHeight) { 95 | super(DimensionConstrain.createAbsolutionDimension(dstWidth, dstHeight)); 96 | } 97 | 98 | public BufferedImage doFilter(BufferedImage img, BufferedImage dest, int dstWidth, int dstHeight) { 99 | BufferedImage ret = img; 100 | int w, h; 101 | 102 | w = img.getWidth(); 103 | h = img.getHeight(); 104 | 105 | do { 106 | if (w > dstWidth) { 107 | w /= 2; 108 | if (w < dstWidth) { 109 | w = dstWidth; 110 | } 111 | } else { 112 | w = dstWidth; 113 | } 114 | 115 | if (h > dstHeight) { 116 | h /= 2; 117 | if (h < dstHeight) { 118 | h = dstHeight; 119 | } 120 | } else { 121 | h = dstHeight; 122 | } 123 | 124 | ResampleOp resizeOp = new ResampleOp(w, h); 125 | resizeOp.setFilter(ResampleFilters.getLanczos3Filter()); 126 | ret = resizeOp.filter(ret, null); 127 | } while (w != dstWidth || h != dstHeight); 128 | 129 | return ret; 130 | } 131 | } 132 | 133 | @Override 134 | public String toString() { 135 | return "ProgressiveAlgorithm[" + type + ']'; 136 | } 137 | 138 | @Override 139 | public boolean equals(Object o) { 140 | if (this == o) return true; 141 | if (o == null || getClass() != o.getClass()) return false; 142 | 143 | ProgressiveAlgorithm that = (ProgressiveAlgorithm) o; 144 | 145 | return type == that.type; 146 | 147 | } 148 | 149 | @Override 150 | public int hashCode() { 151 | return type != null ? type.hashCode() : 0; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/util/PostProcessorUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.util; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | import at.favre.tools.dconvert.arg.ImageType; 21 | import at.favre.tools.dconvert.converters.Result; 22 | import at.favre.tools.dconvert.converters.postprocessing.IPostProcessor; 23 | 24 | import java.io.BufferedReader; 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.io.InputStreamReader; 28 | import java.nio.file.Files; 29 | import java.nio.file.StandardCopyOption; 30 | import java.util.Arrays; 31 | import java.util.Collections; 32 | import java.util.UUID; 33 | import java.util.concurrent.locks.ReentrantLock; 34 | 35 | /** 36 | * Util for post processors 37 | */ 38 | public final class PostProcessorUtil { 39 | private static ReentrantLock lock = new ReentrantLock(); 40 | 41 | private PostProcessorUtil() { 42 | } 43 | 44 | public static Result runImageOptimizer(File rawFile, ImageType processedType, String[] args, boolean keepOriginal) throws IOException { 45 | return runImageOptimizer(rawFile, processedType, args, keepOriginal, MiscUtil.getFileExtension(rawFile)); 46 | } 47 | 48 | public static Result runImageOptimizer(File rawFile, ImageType processedType, String[] args, boolean keepOriginal, String outExtension) throws IOException { 49 | if (Arguments.getImageType(rawFile) == processedType && rawFile.exists() && rawFile.isFile()) { 50 | String id = UUID.randomUUID().toString().substring(0, 8); 51 | 52 | File outFile = getFileWithPostFix(rawFile, "_optimized_" + id, outExtension); 53 | File copy = getFileWithPostFix(rawFile, "_copy_" + id, outExtension); 54 | 55 | Files.copy(rawFile.toPath(), copy.toPath(), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); 56 | 57 | for (int i = 0; i < args.length; i++) { 58 | if (args[i].equals("%%outFilePath%%")) { 59 | args[i] = "\"" + outFile.getAbsolutePath() + "\""; 60 | } 61 | 62 | if (args[i].equals("%%sourceFilePath%%")) { 63 | args[i] = "\"" + copy.getAbsolutePath() + "\""; 64 | } 65 | } 66 | 67 | Result result = runCmd(args); 68 | 69 | copy.delete(); 70 | 71 | boolean r1 = true, r2 = true, r3 = true; 72 | if (outFile.exists() && outFile.isFile()) { 73 | if (keepOriginal) { 74 | File origFile = getFileWithPostFix(rawFile, IPostProcessor.ORIG_POSTFIX, MiscUtil.getFileExtension(rawFile)); 75 | 76 | if (origFile.exists()) { 77 | origFile.delete(); 78 | } 79 | 80 | r1 = rawFile.renameTo(origFile); 81 | 82 | File outFileNew = getFileWithPostFix(rawFile, "", outExtension); 83 | 84 | if (outFileNew.exists()) { 85 | outFileNew.delete(); 86 | } 87 | 88 | r2 = outFile.renameTo(outFileNew); 89 | } else { 90 | if (rawFile.delete()) { 91 | File outFileNew = getFileWithPostFix(rawFile, "", outExtension); 92 | 93 | if (outFileNew.exists()) { 94 | outFileNew.delete(); 95 | } 96 | 97 | r3 = outFile.renameTo(outFileNew); 98 | } 99 | } 100 | } 101 | String log = result.log; 102 | if (!r1 || !r2 || !r3) { 103 | log += "Could not rename all files correctly\n"; 104 | } 105 | 106 | return new Result(log, result.exception, Collections.singletonList(rawFile)); 107 | } 108 | return null; 109 | } 110 | 111 | private static File getFileWithPostFix(File src, String postfix, String extension) { 112 | return new File(src.getParentFile(), MiscUtil.getFileNameWithoutExtension(src) + postfix + "." + extension); 113 | } 114 | 115 | private static Result runCmd(String[] cmdArray) { 116 | StringBuilder logStringBuilder = new StringBuilder(); 117 | Exception exception = null; 118 | try { 119 | logStringBuilder.append("execute: ").append(Arrays.toString(cmdArray)).append("\n"); 120 | ProcessBuilder pb = new ProcessBuilder(cmdArray); 121 | pb.redirectErrorStream(true); 122 | Process process = pb.start(); 123 | try (BufferedReader inStreamReader = new BufferedReader( 124 | new InputStreamReader(process.getInputStream()))) { 125 | String s; 126 | while ((s = inStreamReader.readLine()) != null) { 127 | if (!s.isEmpty()) logStringBuilder.append("\t").append(s).append("\n"); 128 | } 129 | } 130 | process.waitFor(); 131 | } catch (Exception e) { 132 | exception = e; 133 | logStringBuilder.append("error: could not run command - ").append(Arrays.toString(cmdArray)).append(" - ").append(e.getMessage()).append(" - is it set in PATH?\n"); 134 | } 135 | return new Result(logStringBuilder.toString(), exception, Collections.emptyList()); 136 | } 137 | 138 | public static boolean canRunCmd(String[] cmdArray) { 139 | try { 140 | ProcessBuilder pb = new ProcessBuilder(cmdArray); 141 | pb.redirectErrorStream(true); 142 | Process process = pb.start(); 143 | try (BufferedReader inStreamReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { 144 | while ((inStreamReader.readLine()) != null) { 145 | } 146 | } 147 | process.waitFor(); 148 | } catch (Exception e) { 149 | return false; 150 | } 151 | return true; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/util/ImageUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.util; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | import at.favre.tools.dconvert.arg.ImageType; 21 | import com.twelvemonkeys.imageio.metadata.CompoundDirectory; 22 | import com.twelvemonkeys.imageio.metadata.exif.EXIFReader; 23 | import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; 24 | import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment; 25 | import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil; 26 | 27 | import javax.imageio.IIOException; 28 | import javax.imageio.ImageIO; 29 | import javax.imageio.ImageReadParam; 30 | import javax.imageio.ImageReader; 31 | import javax.imageio.metadata.IIOMetadata; 32 | import javax.imageio.stream.FileImageInputStream; 33 | import javax.imageio.stream.ImageInputStream; 34 | import java.awt.*; 35 | import java.awt.image.BufferedImage; 36 | import java.io.File; 37 | import java.io.IOException; 38 | import java.io.InputStream; 39 | import java.util.Iterator; 40 | import java.util.List; 41 | 42 | /** 43 | * Main Util class containing all 44 | */ 45 | public final class ImageUtil { 46 | 47 | private ImageUtil() { 48 | } 49 | 50 | public static LoadedImage loadImage(File input) throws Exception { 51 | if (input == null) { 52 | throw new IllegalArgumentException("input == null!"); 53 | } 54 | if (!input.canRead()) { 55 | throw new IIOException("Can't read input file!"); 56 | } 57 | 58 | ImageInputStream stream = ImageIO.createImageInputStream(input); 59 | if (stream == null) { 60 | throw new IIOException("Can't create an ImageInputStream!"); 61 | } 62 | LoadedImage image = read(stream, Arguments.getImageType(input)); 63 | if (image.getImage() == null) { 64 | stream.close(); 65 | } 66 | return new LoadedImage(input, image.getImage(), image.getMetadata(), readExif(input)); 67 | } 68 | 69 | private static CompoundDirectory readExif(File input) throws IOException { 70 | if (Arguments.getImageType(input) == ImageType.JPG) { 71 | try (ImageInputStream stream = ImageIO.createImageInputStream(input)) { 72 | List exifSegment = JPEGSegmentUtil.readSegments(stream, JPEG.APP1, "Exif"); 73 | if (!exifSegment.isEmpty()) { 74 | InputStream exifData = exifSegment.get(0).data(); 75 | exifData.read(); // Skip 0-pad for Exif in JFIF 76 | try (ImageInputStream exifStream = ImageIO.createImageInputStream(exifData)) { 77 | return (CompoundDirectory) new EXIFReader().read(exifStream); 78 | } 79 | } 80 | } catch (Exception e) { 81 | System.err.println("could not read exif"); 82 | e.printStackTrace(); 83 | return null; 84 | } 85 | } 86 | return null; 87 | } 88 | 89 | private static LoadedImage read(ImageInputStream stream, ImageType imageType) throws IOException { 90 | if (stream == null) { 91 | throw new IllegalArgumentException("stream == null!"); 92 | } 93 | 94 | Iterator iter = ImageIO.getImageReaders(stream); 95 | if (!iter.hasNext()) { 96 | return null; 97 | } 98 | 99 | ImageReader reader = (ImageReader) iter.next(); 100 | ImageReadParam param = reader.getDefaultReadParam(); 101 | reader.setInput(stream, true, true); 102 | BufferedImage bi; 103 | IIOMetadata metadata; 104 | try { 105 | metadata = reader.getImageMetadata(0); 106 | bi = reader.read(0, param); 107 | } finally { 108 | reader.dispose(); 109 | stream.close(); 110 | } 111 | 112 | return new LoadedImage(null, bi, metadata, null); 113 | } 114 | 115 | @Deprecated 116 | public static BufferedImage readSvg(File file, Dimension sourceDimension) throws Exception { 117 | try (ImageInputStream input = ImageIO.createImageInputStream(file)) { 118 | Iterator readers = ImageIO.getImageReaders(input); 119 | if (!readers.hasNext()) { 120 | throw new IllegalArgumentException("No reader for: " + file); 121 | } 122 | 123 | ImageReader reader = readers.next(); 124 | try { 125 | reader.setInput(input); 126 | ImageReadParam param = reader.getDefaultReadParam(); 127 | param.setSourceRenderSize(sourceDimension); 128 | return reader.read(0, param); 129 | } finally { 130 | reader.dispose(); 131 | } 132 | } 133 | } 134 | 135 | /** 136 | * Gets image dimensions for given file 137 | * 138 | * @param imgFile image file 139 | * @return dimensions of image 140 | * @throws IOException if the file is not a known image 141 | */ 142 | public static Dimension getImageDimension(File imgFile) throws IOException { 143 | int pos = imgFile.getName().lastIndexOf("."); 144 | if (pos == -1) 145 | throw new IOException("No extension for file: " + imgFile.getAbsolutePath()); 146 | String suffix = imgFile.getName().substring(pos + 1); 147 | Iterator iter = ImageIO.getImageReadersBySuffix(suffix); 148 | if (iter.hasNext()) { 149 | ImageReader reader = iter.next(); 150 | try { 151 | ImageInputStream stream = new FileImageInputStream(imgFile); 152 | reader.setInput(stream); 153 | int width = reader.getWidth(reader.getMinIndex()); 154 | int height = reader.getHeight(reader.getMinIndex()); 155 | return new Dimension(width, height); 156 | } finally { 157 | reader.dispose(); 158 | } 159 | } 160 | 161 | throw new IOException("Not a known image file: " + imgFile.getAbsolutePath()); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/scaling/ThumbnailnatorProgressiveAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.scaling; 18 | 19 | import net.coobird.thumbnailator.makers.FixedSizeThumbnailMaker; 20 | import net.coobird.thumbnailator.resizers.AbstractResizer; 21 | 22 | import java.awt.*; 23 | import java.awt.image.BufferedImage; 24 | import java.util.Collections; 25 | import java.util.Map; 26 | 27 | /** 28 | * Algorithms from Thumbnailnator 29 | */ 30 | public class ThumbnailnatorProgressiveAlgorithm implements ScaleAlgorithm { 31 | 32 | private Object interpolationValue; 33 | 34 | public ThumbnailnatorProgressiveAlgorithm(Object interpolationValue) { 35 | this.interpolationValue = interpolationValue; 36 | } 37 | 38 | @Override 39 | public BufferedImage scale(BufferedImage imageToScale, int dWidth, int dHeight) { 40 | return new FixedSizeThumbnailMaker(dWidth, dHeight, false, true) 41 | .resizer(new ProgressiveResizer(interpolationValue)).make(imageToScale); 42 | } 43 | 44 | public static class ProgressiveResizer extends AbstractResizer { 45 | public ProgressiveResizer(Object interpolationValue) { 46 | this(interpolationValue, Collections.emptyMap()); 47 | } 48 | 49 | public ProgressiveResizer(Object interpolationValue, Map hints) { 50 | super(interpolationValue, hints); 51 | checkArg(interpolationValue); 52 | } 53 | 54 | private void checkArg(Object interpolationValue) { 55 | if (interpolationValue != RenderingHints.VALUE_INTERPOLATION_BICUBIC && 56 | interpolationValue != RenderingHints.VALUE_INTERPOLATION_BILINEAR && 57 | interpolationValue != RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR) 58 | throw new IllegalArgumentException("wrong argument passed muts be one of RenderingHints.VALUE_INTERPOLATION_BICUBIC, " + 59 | "RenderingHints.VALUE_INTERPOLATION_BILINEAR or RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR"); 60 | } 61 | 62 | @Override 63 | public void resize(BufferedImage srcImage, BufferedImage destImage) 64 | throws NullPointerException { 65 | super.performChecks(srcImage, destImage); 66 | 67 | int currentWidth = srcImage.getWidth(); 68 | int currentHeight = srcImage.getHeight(); 69 | 70 | final int targetWidth = destImage.getWidth(); 71 | final int targetHeight = destImage.getHeight(); 72 | 73 | // If multi-step downscaling is not required, perform one-step. 74 | if ((targetWidth * 2 >= currentWidth) && (targetHeight * 2 >= currentHeight)) { 75 | super.resize(srcImage, destImage); 76 | return; 77 | } 78 | 79 | // Temporary image used for in-place resizing of image. 80 | BufferedImage tempImage = new BufferedImage( 81 | currentWidth, 82 | currentHeight, 83 | destImage.getType() 84 | ); 85 | 86 | Graphics2D g = tempImage.createGraphics(); 87 | g.setRenderingHints(RENDERING_HINTS); 88 | g.setComposite(AlphaComposite.Src); 89 | 90 | /* 91 | * Determine the size of the first resize step should be. 92 | * 1) Beginning from the target size 93 | * 2) Increase each dimension by 2 94 | * 3) Until reaching the original size 95 | */ 96 | 97 | int startWidth = targetWidth; 98 | int startHeight = targetHeight; 99 | 100 | while (startWidth < currentWidth && startHeight < currentHeight) { 101 | startWidth *= 2; 102 | startHeight *= 2; 103 | } 104 | 105 | currentWidth = startWidth / 2; 106 | currentHeight = startHeight / 2; 107 | 108 | // Perform first resize step. 109 | g.drawImage(srcImage, 0, 0, currentWidth, currentHeight, null); 110 | 111 | // Perform an in-place progressive bilinear resize. 112 | while ((currentWidth >= targetWidth * 2) && (currentHeight >= targetHeight * 2)) { 113 | currentWidth /= 2; 114 | currentHeight /= 2; 115 | 116 | if (currentWidth < targetWidth) { 117 | currentWidth = targetWidth; 118 | } 119 | if (currentHeight < targetHeight) { 120 | currentHeight = targetHeight; 121 | } 122 | 123 | g.drawImage( 124 | tempImage, 125 | 0, 0, currentWidth, currentHeight, 126 | 0, 0, currentWidth * 2, currentHeight * 2, 127 | null 128 | ); 129 | } 130 | 131 | g.dispose(); 132 | 133 | // Draw the resized image onto the destination image. 134 | Graphics2D destg = destImage.createGraphics(); 135 | destg.drawImage(tempImage, 0, 0, targetWidth, targetHeight, 0, 0, currentWidth, currentHeight, null); 136 | destg.dispose(); 137 | } 138 | } 139 | 140 | @Override 141 | public String toString() { 142 | return "ThumbnailnatorProgressiveAlgorithm{" + 143 | "interpolationValue=" + interpolationValue + 144 | '}'; 145 | } 146 | 147 | @Override 148 | public boolean equals(Object o) { 149 | if (this == o) return true; 150 | if (o == null || getClass() != o.getClass()) return false; 151 | 152 | ThumbnailnatorProgressiveAlgorithm that = (ThumbnailnatorProgressiveAlgorithm) o; 153 | 154 | return interpolationValue != null ? interpolationValue.equals(that.interpolationValue) : that.interpolationValue == null; 155 | 156 | } 157 | 158 | @Override 159 | public int hashCode() { 160 | return interpolationValue != null ? interpolationValue.hashCode() : 0; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.1.1 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM Provide a "standardized" way to retrieve the CLI args that will 157 | @REM work with both Windows and non-Windows executions. 158 | set MAVEN_CMD_LINE_ARGS=%* 159 | 160 | %MAVEN_JAVA_EXE% ^ 161 | %JVM_CONFIG_MAVEN_PROPS% ^ 162 | %MAVEN_OPTS% ^ 163 | %MAVEN_DEBUG_OPTS% ^ 164 | -classpath %WRAPPER_JAR% ^ 165 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 166 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 167 | if ERRORLEVEL 1 goto error 168 | goto end 169 | 170 | :error 171 | set ERROR_CODE=1 172 | 173 | :end 174 | @endlocal & set ERROR_CODE=%ERROR_CODE% 175 | 176 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 177 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 178 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 179 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 180 | :skipRcPost 181 | 182 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 183 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 184 | 185 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 186 | 187 | cmd /C exit /B %ERROR_CODE% 188 | -------------------------------------------------------------------------------- /src/test/java/at/favre/tools/dconvert/test/AndroidConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package at.favre.tools.dconvert.test; 19 | 20 | import at.favre.tools.dconvert.arg.Arguments; 21 | import at.favre.tools.dconvert.arg.EPlatform; 22 | import at.favre.tools.dconvert.arg.ImageType; 23 | import at.favre.tools.dconvert.converters.AndroidConverter; 24 | import at.favre.tools.dconvert.util.ImageUtil; 25 | import at.favre.tools.dconvert.util.MiscUtil; 26 | import org.junit.Test; 27 | 28 | import java.awt.*; 29 | import java.io.File; 30 | import java.io.IOException; 31 | import java.util.ArrayList; 32 | import java.util.Collections; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.stream.Collectors; 36 | 37 | import static org.junit.Assert.*; 38 | 39 | /** 40 | * Unit test of the {@link at.favre.tools.dconvert.converters.IPlatformConverter} for android 41 | */ 42 | public class AndroidConverterTest extends AConverterTest { 43 | 44 | @Test 45 | public void testMipmapFolder() throws Exception { 46 | List files = copyToTestPath(defaultSrc, "png_example1_alpha_144.png"); 47 | test(new Arguments.Builder(defaultSrc, DEFAULT_SCALE).dstFolder(defaultDst).createMipMapInsteadOfDrawableDir(true).includeAndroidLdpiTvdpi(true).platform(Collections.singleton(getType())).build(), files); 48 | } 49 | 50 | @Test 51 | public void testLdpiAndTvdpi() throws Exception { 52 | List files = copyToTestPath(defaultSrc, "png_example1_alpha_144.png"); 53 | test(new Arguments.Builder(defaultSrc, DEFAULT_SCALE).dstFolder(defaultDst).includeAndroidLdpiTvdpi(true).platform(Collections.singleton(getType())).build(), files); 54 | } 55 | 56 | @Test 57 | public void testSingleNinePatch() throws Exception { 58 | List files = copyToTestPath(defaultSrc, "ninepatch_bubble.9.png"); 59 | defaultTest(files); 60 | } 61 | 62 | @Override 63 | protected EPlatform getType() { 64 | return EPlatform.ANDROID; 65 | } 66 | 67 | @Override 68 | protected void checkOutDir(File dstDir, Arguments arguments, List files, EPlatform type) throws IOException { 69 | checkOutDirAndroid(dstDir, arguments, files); 70 | } 71 | 72 | public static void checkOutDirAndroid(File dstDir, Arguments arguments, List files) throws IOException { 73 | Map dimensionMap = createDimensionMap(files); 74 | 75 | List expectedDirs = new ArrayList<>(); 76 | 77 | expectedDirs.addAll(AndroidConverter.getAndroidDensityDescriptors(arguments) 78 | .stream() 79 | .map(androidDensityDescriptor -> new DensityFolder(androidDensityDescriptor.folderName, androidDensityDescriptor.scale)).collect(Collectors.toList())); 80 | 81 | assertFalse("expected dirs should not be empty", expectedDirs.isEmpty()); 82 | if (!files.isEmpty()) { 83 | assertFalse("output dir should not be empty", dstDir.list() == null && dstDir.list().length == 0); 84 | 85 | System.out.println("Android-convert " + files); 86 | 87 | for (String path : dstDir.list()) { 88 | expectedDirs.stream().filter(expectedDir -> expectedDir.folderName.equals(path)).forEach(expectedDir -> { 89 | try { 90 | expectedDir.found = true; 91 | 92 | List expectedFiles = createExpectedFilesMap(arguments, new File(dstDir, path), files); 93 | 94 | assertTrue("files count should match input", files.isEmpty() == expectedFiles.isEmpty()); 95 | 96 | for (ImageCheck expectedFile : expectedFiles) { 97 | for (File imageFile : new File(dstDir, path).listFiles()) { 98 | if (expectedFile.targetFile.equals(imageFile)) { 99 | expectedFile.found = true; 100 | Dimension expectedDimension = getScaledDimension(expectedFile.srcFile, arguments, dimensionMap.get(expectedFile.srcFile), expectedDir.scaleFactor, expectedFile.isNinepatch); 101 | Dimension actualDimensions = ImageUtil.getImageDimension(imageFile); 102 | assertEquals("height should match", expectedDimension.getHeight(), actualDimensions.getHeight(), expectedFile.isNinepatch ? 15 : 0.1); 103 | assertEquals("width should match", expectedDimension.getWidth(), actualDimensions.getWidth(), expectedFile.isNinepatch ? 15 : 0.1); 104 | } 105 | } 106 | } 107 | 108 | for (ImageCheck expectedFile : expectedFiles) { 109 | assertTrue(expectedFile.targetFile + " file should be generated in path", expectedFile.found); 110 | } 111 | System.out.print("found " + expectedFiles.size() + " files in " + expectedDir.folderName + ", "); 112 | } catch (Exception e) { 113 | fail(); 114 | e.printStackTrace(); 115 | } 116 | }); 117 | 118 | } 119 | 120 | for (DensityFolder expectedDir : expectedDirs) { 121 | assertTrue(expectedDir.folderName + " should be generated in path", expectedDir.found); 122 | } 123 | 124 | System.out.println(); 125 | } else { 126 | assertTrue(dstDir.list() == null || dstDir.list().length == 0); 127 | } 128 | } 129 | 130 | private static List createExpectedFilesMap(Arguments arguments, File file, List files) throws IOException { 131 | List expectedFiles = new ArrayList<>(); 132 | 133 | for (File srcImageFile : files) { 134 | for (ImageType.ECompression compression : Arguments.getOutCompressionForType(arguments.outputCompressionMode, Arguments.getImageType(srcImageFile))) { 135 | expectedFiles.add(new ImageCheck(srcImageFile, new File(file, MiscUtil.getFileNameWithoutExtension(srcImageFile) + "." + compression.extension))); 136 | } 137 | } 138 | 139 | return expectedFiles; 140 | } 141 | 142 | private static class ImageCheck { 143 | public final File srcFile; 144 | public final File targetFile; 145 | public final boolean isNinepatch; 146 | public boolean found; 147 | 148 | public ImageCheck(File srcFile, File targetFile) throws IOException { 149 | this.srcFile = srcFile; 150 | this.targetFile = targetFile; 151 | this.isNinepatch = AndroidConverter.isNinePatch(srcFile); 152 | } 153 | } 154 | 155 | private static class DensityFolder { 156 | public final String folderName; 157 | public final float scaleFactor; 158 | public boolean found; 159 | 160 | public DensityFolder(String folderName, float scaleFactor) { 161 | this.folderName = folderName; 162 | this.scaleFactor = scaleFactor; 163 | } 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/converters/scaling/ImageHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.converters.scaling; 18 | 19 | import at.favre.tools.dconvert.arg.Arguments; 20 | import at.favre.tools.dconvert.arg.EScalingAlgorithm; 21 | import at.favre.tools.dconvert.arg.ImageType; 22 | import at.favre.tools.dconvert.util.LoadedImage; 23 | import at.favre.tools.dconvert.util.MiscUtil; 24 | import at.favre.tools.dconvert.util.NinePatchScaler; 25 | import com.twelvemonkeys.imageio.metadata.CompoundDirectory; 26 | 27 | import javax.imageio.IIOImage; 28 | import javax.imageio.ImageIO; 29 | import javax.imageio.ImageWriteParam; 30 | import javax.imageio.ImageWriter; 31 | import javax.imageio.stream.FileImageOutputStream; 32 | import javax.imageio.stream.ImageOutputStream; 33 | import java.awt.*; 34 | import java.awt.image.BufferedImage; 35 | import java.awt.image.ConvolveOp; 36 | import java.awt.image.Kernel; 37 | import java.io.File; 38 | import java.io.IOException; 39 | import java.util.List; 40 | import java.util.*; 41 | import java.util.stream.Collectors; 42 | 43 | /** 44 | * Handles scaling and writing/compression images to disk 45 | */ 46 | public class ImageHandler { 47 | private static final Color DEFAULT_COLOR = Color.white; 48 | public static final boolean TEST_MODE = false; 49 | public static final ConvolveOp OP_ANTIALIAS = new ConvolveOp(new Kernel(3, 3, new float[]{.0f, .08f, .0f, .08f, .68f, .08f, .0f, .08f, .0f}), ConvolveOp.EDGE_NO_OP, null); 50 | public static final Map traceMap = new HashMap<>(); 51 | private final Arguments args; 52 | 53 | public ImageHandler(Arguments args) { 54 | this.args = args; 55 | } 56 | 57 | public List saveToFile(File targetFile, LoadedImage imageData, Dimension targetDimension, boolean isNinePatch) throws Exception { 58 | 59 | List files = new ArrayList<>(2); 60 | List compressionList = Arguments.getOutCompressionForType(args.outputCompressionMode, Arguments.getImageType(imageData.getSourceFile())); 61 | for (ImageType.ECompression compression : compressionList) { 62 | File imageFile = new File(targetFile.getAbsolutePath() + "." + compression.extension); 63 | 64 | if (imageFile.exists() && args.skipExistingFiles) { 65 | break; 66 | } 67 | 68 | List algorithms = getScaleAlgorithm(getScalingAlgorithm(getScalingType(imageData, targetDimension)), getScalingType(imageData, targetDimension)); 69 | 70 | for (ScaleAlgorithm scaleAlgorithm : algorithms) { 71 | 72 | if (!traceMap.containsKey(scaleAlgorithm)) { 73 | traceMap.put(scaleAlgorithm, 0L); 74 | } 75 | 76 | BufferedImage scaledImage; 77 | if (isNinePatch && compression == ImageType.ECompression.PNG) { 78 | scaledImage = new NinePatchScaler().scale(imageData.getImage(), targetDimension, getAsScalingAlgorithm(scaleAlgorithm, compression)); 79 | } else { 80 | long startNanos = System.nanoTime(); 81 | scaledImage = scale(scaleAlgorithm, imageData.getImage(), targetDimension.width, targetDimension.height, compression, DEFAULT_COLOR); 82 | traceMap.put(scaleAlgorithm, traceMap.get(scaleAlgorithm) + (System.nanoTime() - startNanos)); 83 | } 84 | 85 | File fileToSave = imageFile; 86 | 87 | if (algorithms.size() > 1) { 88 | fileToSave = new File(imageFile.getParentFile(), MiscUtil.getFileNameWithoutExtension(imageFile) + "." + scaleAlgorithm.toString() + "." + MiscUtil.getFileExtension(imageFile)); 89 | } 90 | 91 | if (compression == ImageType.ECompression.JPG) { 92 | compressJpeg(scaledImage, null, args.compressionQuality, fileToSave); 93 | } else { 94 | ImageIO.write(scaledImage, compression.name().toLowerCase(), fileToSave); 95 | } 96 | scaledImage.flush(); 97 | files.add(imageFile); 98 | } 99 | } 100 | return files; 101 | } 102 | 103 | private void compressJpeg(BufferedImage bufferedImage, CompoundDirectory exif, float quality, File targetFile) throws IOException { 104 | ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next(); 105 | ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam(); 106 | jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); 107 | jpgWriteParam.setCompressionQuality(quality); 108 | 109 | ImageWriter writer = null; 110 | try (ImageOutputStream outputStream = new FileImageOutputStream(targetFile)) { 111 | writer = ImageIO.getImageWritersByFormatName("jpg").next(); 112 | writer.setOutput(outputStream); 113 | writer.write(null, new IIOImage(bufferedImage, null, null), jpgWriteParam); 114 | } finally { 115 | if (writer != null) writer.dispose(); 116 | } 117 | } 118 | 119 | private EScalingAlgorithm getScalingAlgorithm(EScalingAlgorithm.Type type) { 120 | return type == EScalingAlgorithm.Type.UPSCALING ? args.upScalingAlgorithm : args.downScalingAlgorithm; 121 | } 122 | 123 | private EScalingAlgorithm.Type getScalingType(LoadedImage imageData, Dimension targetDimension) { 124 | long targetSize = targetDimension.height * targetDimension.width; 125 | long sourceSize = imageData.getImage().getHeight() * imageData.getImage().getWidth(); 126 | return targetSize >= sourceSize ? EScalingAlgorithm.Type.UPSCALING : EScalingAlgorithm.Type.DOWNSCALING; 127 | } 128 | 129 | private List getScaleAlgorithm(EScalingAlgorithm algorithm, EScalingAlgorithm.Type type) { 130 | if (TEST_MODE) { 131 | return EScalingAlgorithm.getAllEnabled().stream().filter(eScalingAlgorithm -> eScalingAlgorithm.getSupportedForType().contains(type)).map(EScalingAlgorithm::getImplementation).collect(Collectors.toList()); 132 | } else { 133 | return Collections.singletonList(algorithm.getImplementation()); 134 | } 135 | } 136 | 137 | private BufferedImage scale(ScaleAlgorithm scaleAlgorithm, BufferedImage imageToScale, int dWidth, int dHeight, ImageType.ECompression compression, Color background) { 138 | 139 | BufferedImage scaledImage; 140 | 141 | if (dWidth == imageToScale.getWidth() && dHeight == imageToScale.getHeight()) { 142 | scaledImage = imageToScale; 143 | } else { 144 | scaledImage = scaleAlgorithm.scale(imageToScale, dWidth, dHeight); 145 | } 146 | 147 | if (!compression.hasTransparency) { 148 | BufferedImage convertedImg = new BufferedImage(scaledImage.getWidth(), scaledImage.getHeight(), BufferedImage.TYPE_INT_RGB); 149 | convertedImg.getGraphics().drawImage(scaledImage, 0, 0, background, null); 150 | scaledImage = convertedImg; 151 | } 152 | 153 | if (args.enableAntiAliasing) { 154 | scaledImage = OP_ANTIALIAS.filter(scaledImage, null); 155 | } 156 | 157 | return scaledImage; 158 | } 159 | 160 | private ScaleAlgorithm getAsScalingAlgorithm(final ScaleAlgorithm algorithm, ImageType.ECompression compression) { 161 | return (imageToScale, dWidth, dHeight) -> ImageHandler.this.scale(algorithm, imageToScale, dWidth, dHeight, compression, DEFAULT_COLOR); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/at/favre/tools/dconvert/util/NinePatchScaler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Patrick Favre-Bulle 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package at.favre.tools.dconvert.util; 18 | 19 | import at.favre.tools.dconvert.converters.scaling.ScaleAlgorithm; 20 | import at.favre.tools.dconvert.converters.scaling.ThumbnailnatorProgressiveAlgorithm; 21 | import at.favre.tools.dconvert.exceptions.NinePatchException; 22 | 23 | import java.awt.*; 24 | import java.awt.image.BufferedImage; 25 | 26 | /** 27 | * Scales 9-patches correctly, keeping the 1px border intact. 28 | *

29 | * Adapted from Github 30 | * 31 | * @author Redwarp, pfavre 32 | */ 33 | public class NinePatchScaler { 34 | 35 | private ScaleAlgorithm algorithm; 36 | private ScaleAlgorithm borderScalerAlgorithm = new ThumbnailnatorProgressiveAlgorithm(RenderingHints.VALUE_INTERPOLATION_BILINEAR); 37 | 38 | public BufferedImage scale(BufferedImage inputImage, Dimension dimensions, ScaleAlgorithm algorithm) throws NinePatchException { 39 | this.algorithm = algorithm; 40 | BufferedImage trimmedImage = this.trim9PBorder(inputImage); 41 | 42 | trimmedImage = algorithm.scale(trimmedImage, dimensions.width, dimensions.height); 43 | 44 | BufferedImage borderImage; 45 | 46 | int w = trimmedImage.getWidth(); 47 | int h = trimmedImage.getHeight(); 48 | 49 | borderImage = this.generateBordersImage(inputImage, w, h); 50 | 51 | int[] rgbArray = new int[w * h]; 52 | trimmedImage.getRGB(0, 0, w, h, rgbArray, 0, w); 53 | borderImage.setRGB(1, 1, w, h, rgbArray, 0, w); 54 | rgbArray = null; 55 | 56 | return borderImage; 57 | } 58 | 59 | private BufferedImage trim9PBorder(BufferedImage inputImage) { 60 | BufferedImage trimedImage = new BufferedImage(inputImage.getWidth() - 2, inputImage.getHeight() - 2, BufferedImage.TYPE_INT_ARGB); 61 | Graphics2D g = trimedImage.createGraphics(); 62 | g.drawImage(inputImage, 0, 0, trimedImage.getWidth(), trimedImage.getHeight(), 1, 1, inputImage.getWidth() - 1, inputImage.getHeight() - 1, null); 63 | g.dispose(); 64 | return trimedImage; 65 | } 66 | 67 | private void enforceBorderColors(BufferedImage inputImage) { 68 | Graphics2D g = inputImage.createGraphics(); 69 | g.setBackground(new Color(0, 0, 0, 0)); 70 | g.clearRect(1, 1, inputImage.getWidth() - 2, inputImage.getHeight() - 2); 71 | g.dispose(); 72 | int w = inputImage.getWidth(); 73 | int h = inputImage.getHeight(); 74 | int[] rgb = new int[w * h]; 75 | 76 | inputImage.getRGB(0, 0, w, h, rgb, 0, w); 77 | 78 | for (int i = 0; i < rgb.length; i++) { 79 | if ((0xff000000 & rgb[i]) != 0) { 80 | rgb[i] = 0xff000000; 81 | } 82 | } 83 | inputImage.setRGB(0, 0, w, h, rgb, 0, w); 84 | inputImage.setRGB(0, 0, 0x0); 85 | inputImage.setRGB(0, h - 1, 0x0); 86 | inputImage.setRGB(w - 1, h - 1, 0x0); 87 | inputImage.setRGB(w - 1, 0, 0x0); 88 | } 89 | 90 | private BufferedImage generateBordersImage(BufferedImage source, int trimedWidth, int trimedHeight) throws NinePatchException { 91 | BufferedImage finalBorder = new BufferedImage(trimedWidth + 2, trimedHeight + 2, BufferedImage.TYPE_INT_ARGB); 92 | int cutW = source.getWidth() - 2; 93 | int cutH = source.getHeight() - 2; 94 | // left border 95 | BufferedImage leftBorder = new BufferedImage(1, cutH, BufferedImage.TYPE_INT_ARGB); 96 | leftBorder.setRGB(0, 0, 1, cutH, source.getRGB(0, 1, 1, cutH, null, 0, 1), 0, 1); 97 | this.verifyBorderImage(leftBorder); 98 | leftBorder = this.resizeBorder(leftBorder, 1, trimedHeight); 99 | finalBorder.setRGB(0, 1, 1, trimedHeight, leftBorder.getRGB(0, 0, 1, trimedHeight, null, 0, 1), 0, 1); 100 | // right border 101 | BufferedImage rightBorder = new BufferedImage(1, cutH, BufferedImage.TYPE_INT_ARGB); 102 | rightBorder.setRGB(0, 0, 1, cutH, source.getRGB(cutW + 1, 1, 1, cutH, null, 0, 1), 0, 1); 103 | this.verifyBorderImage(rightBorder); 104 | rightBorder = this.resizeBorder(rightBorder, 1, trimedHeight); 105 | finalBorder.setRGB(trimedWidth + 1, 1, 1, trimedHeight, rightBorder.getRGB(0, 0, 1, trimedHeight, null, 0, 1), 0, 1); 106 | // top border 107 | BufferedImage topBorder = new BufferedImage(cutW, 1, BufferedImage.TYPE_INT_ARGB); 108 | topBorder.setRGB(0, 0, cutW, 1, source.getRGB(1, 0, cutW, 1, null, 0, cutW), 0, cutW); 109 | this.verifyBorderImage(topBorder); 110 | topBorder = this.resizeBorder(topBorder, trimedWidth, 1); 111 | finalBorder.setRGB(1, 0, trimedWidth, 1, topBorder.getRGB(0, 0, trimedWidth, 1, null, 0, trimedWidth), 0, trimedWidth); 112 | // bottom border 113 | BufferedImage bottomBorder = new BufferedImage(cutW, 1, BufferedImage.TYPE_INT_ARGB); 114 | bottomBorder.setRGB(0, 0, cutW, 1, source.getRGB(1, cutH + 1, cutW, 1, null, 0, cutW), 0, cutW); 115 | this.verifyBorderImage(bottomBorder); 116 | bottomBorder = this.resizeBorder(bottomBorder, trimedWidth, 1); 117 | finalBorder.setRGB(1, trimedHeight + 1, trimedWidth, 1, bottomBorder.getRGB(0, 0, trimedWidth, 1, null, 0, trimedWidth), 0, trimedWidth); 118 | 119 | return finalBorder; 120 | } 121 | 122 | private BufferedImage resizeBorder(final BufferedImage border, int targetWidth, int targetHeight) { 123 | if (targetWidth > border.getWidth() 124 | || targetHeight > border.getHeight()) { 125 | BufferedImage endImage = borderScalerAlgorithm.scale(border, targetWidth, targetHeight); 126 | this.enforceBorderColors(endImage); 127 | return endImage; 128 | } 129 | 130 | int w = border.getWidth(); 131 | int h = border.getHeight(); 132 | int[] data = border.getRGB(0, 0, w, h, null, 0, w); 133 | int[] newData = new int[targetWidth * targetHeight]; 134 | 135 | float widthRatio = (float) Math.max(targetWidth - 1, 1) 136 | / (float) Math.max(w - 1, 1); 137 | float heightRatio = (float) Math.max(targetHeight - 1, 1) 138 | / (float) Math.max(h - 1, 1); 139 | 140 | for (int y = 0; y < h; y++) { 141 | for (int x = 0; x < w; x++) { 142 | if ((0xff000000 & data[y * w + x]) != 0) { 143 | int newX = Math.min(Math.round(x * widthRatio), targetWidth - 1); 144 | int newY = Math.min(Math.round(y * heightRatio), targetHeight - 1); 145 | 146 | newData[newY * targetWidth + newX] = data[y * w + x]; 147 | } 148 | } 149 | } 150 | 151 | BufferedImage img = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB); 152 | img.setRGB(0, 0, targetWidth, targetHeight, newData, 0, targetWidth); 153 | 154 | return img; 155 | } 156 | 157 | private void verifyBorderImage(BufferedImage border) 158 | throws NinePatchException { 159 | int[] rgb = border.getRGB(0, 0, border.getWidth(), border.getHeight(), 160 | null, 0, border.getWidth()); 161 | for (int i = 0; i < rgb.length; i++) { 162 | if ((0xff000000 & rgb[i]) != 0) { 163 | if (rgb[i] != 0xff000000 && rgb[i] != 0xffff0000) { 164 | throw new NinePatchException(); 165 | } 166 | } 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/test/java/at/favre/tools/dconvert/test/WorkerHandlerTest.java: -------------------------------------------------------------------------------- 1 | package at.favre.tools.dconvert.test; 2 | 3 | import at.favre.tools.dconvert.WorkerHandler; 4 | import at.favre.tools.dconvert.arg.Arguments; 5 | import at.favre.tools.dconvert.arg.EPlatform; 6 | import at.favre.tools.dconvert.converters.AndroidConverter; 7 | import at.favre.tools.dconvert.converters.IPlatformConverter; 8 | import at.favre.tools.dconvert.converters.postprocessing.IPostProcessor; 9 | import at.favre.tools.dconvert.test.helper.MockException; 10 | import at.favre.tools.dconvert.test.helper.MockProcessor; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | 14 | import java.io.File; 15 | import java.util.*; 16 | import java.util.concurrent.CountDownLatch; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertTrue; 21 | 22 | /** 23 | * Test for worker handler 24 | */ 25 | public class WorkerHandlerTest extends AIntegrationTest { 26 | 27 | private Random random; 28 | 29 | @Before 30 | public void setUp() throws Exception { 31 | super.setUp(); 32 | random = new Random(12363012L); 33 | } 34 | 35 | @Test 36 | public void testZeroFilesInput() throws Exception { 37 | TestCallback callback = new TestCallback(0, Collections.emptyList(), false, latch); 38 | new WorkerHandler<>(Collections.singletonList(new MockProcessor()), arguments, callback).start(Collections.emptyList()); 39 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 40 | checkResult(callback); 41 | } 42 | 43 | @Test 44 | public void test66FilesInput() throws Exception { 45 | List files = createFiles(66); 46 | TestCallback callback = new TestCallback(files.size(), Collections.emptyList(), false, latch); 47 | new WorkerHandler<>(Collections.singletonList(new MockProcessor()), arguments, callback).start(files); 48 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 49 | checkResult(callback); 50 | } 51 | 52 | @Test 53 | public void test33With3ProcessorsFilesInput() throws Exception { 54 | List files = createFiles(33); 55 | List postProcessors = createProcessors(3); 56 | TestCallback callback = new TestCallback(files.size() * postProcessors.size(), Collections.emptyList(), false, latch); 57 | new WorkerHandler<>(postProcessors, arguments, callback).start(files); 58 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 59 | checkResult(callback); 60 | } 61 | 62 | @Test 63 | public void test5With33ProcessorsFilesInput() throws Exception { 64 | List files = createFiles(5); 65 | List postProcessors = createProcessors(33); 66 | TestCallback callback = new TestCallback(files.size() * postProcessors.size(), Collections.emptyList(), false, latch); 67 | new WorkerHandler<>(postProcessors, arguments, callback).start(files); 68 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 69 | checkResult(callback); 70 | } 71 | 72 | @Test 73 | public void testShouldHaveException() throws Exception { 74 | List files = createFiles(1); 75 | Exception exception = new MockException(); 76 | TestCallback callback = new TestCallback(files.size(), Collections.singletonList(exception), false, latch); 77 | new WorkerHandler<>(Collections.singletonList(new MockProcessor(exception)), arguments, callback).start(files); 78 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 79 | checkResult(callback); 80 | } 81 | 82 | @Test 83 | public void testShouldHave5Exception() throws Exception { 84 | List exception = Arrays.asList(new MockException(), new MockException(), new MockException(), new MockException(), new MockException()); 85 | List files = createFiles(exception.size()); 86 | TestCallback callback = new TestCallback(files.size(), exception, false, latch); 87 | new WorkerHandler<>(Collections.singletonList(new MockProcessor(new MockException())), arguments, callback).start(files); 88 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 89 | checkResult(callback); 90 | } 91 | 92 | @Test 93 | public void testShouldHaltOnException() throws Exception { 94 | List exception = Arrays.asList(new MockException()); 95 | List files = createFiles(exception.size()); 96 | TestCallback callback = new TestCallback(files.size(), exception, true, latch); 97 | new WorkerHandler<>(Collections.singletonList(new MockProcessor(new MockException())), 98 | new Arguments.Builder(null, Arguments.DEFAULT_SCALE).threadCount(4).haltOnError(true).skipParamValidation(true).build(), callback).start(files); 99 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 100 | checkResult(callback); 101 | } 102 | 103 | @Test 104 | public void testAndroidConverterInHandler() throws Exception { 105 | List files = AConverterTest.copyToTestPath(src, "png_example2_alpha_144.png", "gif_example_640.gif", "jpg_example_1920.jpg"); 106 | Arguments arg = new Arguments.Builder(src, Arguments.DEFAULT_SCALE).dstFolder(dst).platform(Collections.singleton(EPlatform.ANDROID)).threadCount(4).build(); 107 | TestCallback callback = new TestCallback(files.size(), Collections.emptyList(), false, latch); 108 | new WorkerHandler<>(Collections.singletonList(new AndroidConverter()), arg, callback).start(files); 109 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 110 | checkResult(callback); 111 | AndroidConverterTest.checkOutDirAndroid(dst, arg, files); 112 | } 113 | 114 | @Test 115 | public void testAllPlatformConverterInHandler() throws Exception { 116 | List files = AConverterTest.copyToTestPath(src, "png_example3_alpha_128.png", "png_example1_alpha_144.png", "jpg_example2_512.jpg"); 117 | List converters = EPlatform.getAllConverters(); 118 | Arguments arg = new Arguments.Builder(src, Arguments.DEFAULT_SCALE).platform(EPlatform.getAll()).dstFolder(dst).threadCount(4).build(); 119 | TestCallback callback = new TestCallback(files.size() * converters.size(), Collections.emptyList(), false, latch); 120 | new WorkerHandler<>(converters, arg, callback).start(files); 121 | assertTrue(latch.await(WAIT_SEC, TimeUnit.SECONDS)); 122 | checkResult(callback); 123 | AConverterTest.checkMultiPlatformConvert(dst, arg, files); 124 | } 125 | 126 | private void checkResult(TestCallback callback) { 127 | assertEquals(callback.expectedJobs, callback.actualJobs); 128 | assertEquals(callback.expectedExceptions, callback.actualExceptions); 129 | assertEquals(callback.expectedHaltDuringProcess, callback.actualHaltDuringProcess); 130 | } 131 | 132 | private List createFiles(int count) { 133 | List files = new ArrayList<>(count); 134 | for (int i = 0; i < count; i++) { 135 | files.add(new File("mock" + i)); 136 | } 137 | return files; 138 | } 139 | 140 | private List createProcessors(int count) { 141 | List processors = new ArrayList<>(count); 142 | for (int i = 0; i < count; i++) { 143 | processors.add(new MockProcessor(22 + random.nextInt(50))); 144 | } 145 | return processors; 146 | } 147 | 148 | private static class TestCallback implements WorkerHandler.Callback { 149 | private final int expectedJobs; 150 | private final List expectedExceptions; 151 | private final boolean expectedHaltDuringProcess; 152 | private final CountDownLatch latch; 153 | private int actualJobs; 154 | private List actualExceptions; 155 | private boolean actualHaltDuringProcess; 156 | 157 | public TestCallback(int expectedJobs, List expectedExceptions, boolean expectedHaltDuringProcess, CountDownLatch latch) { 158 | this.expectedJobs = expectedJobs; 159 | this.expectedExceptions = expectedExceptions; 160 | this.expectedHaltDuringProcess = expectedHaltDuringProcess; 161 | this.latch = latch; 162 | } 163 | 164 | @Override 165 | public void onProgress(float percent) { 166 | } 167 | 168 | @Override 169 | public void onFinished(int finishedJobs, List outFiles, StringBuilder log, List exceptions, boolean haltedDuringProcess) { 170 | actualJobs = finishedJobs; 171 | actualExceptions = exceptions; 172 | actualHaltDuringProcess = haltedDuringProcess; 173 | latch.countDown(); 174 | } 175 | } 176 | } 177 | --------------------------------------------------------------------------------