* 各パーツ情報をもとに非同期にイメージを合成する
* @author seraphy
*/
-public class AsyncImageBuilder extends ImageBuilder implements Runnable {
+public class AsyncImageBuilder implements ImageBuilder, Runnable {
/**
* ロガー
* @param ticket このイメージビルダでリクエストを受け付けた通し番号
*/
void onQueueing(long ticket);
-
+
/**
* リクエストを処理するまえに破棄された場合に呼び出される.<br>
*/
void onAbandoned();
}
-
+
/**
* 同期オブジェクト
*/
private final Object lock = new Object();
-
+
/**
* チケットのシリアルナンバー.<br>
* リクエストがあるごとにインクリメントされる.<br>
*/
private long ticketSerialNum = 0;
-
+
/**
* リクエストされているジョブ、なければnull
*/
* 停止フラグ(volatile)
*/
private volatile boolean stopFlag;
-
+
/**
* スレッド
*/
private Thread thread;
-
+
+ /**
+ * イメージビルダ
+ */
+ private ImageBuilder imageBuilder;
+
/**
* イメージローダを指定して構築する.
* @param imageLoader イメージローダー
*/
public AsyncImageBuilder(ColorConvertedImageCachedLoader imageLoader) {
- super(imageLoader);
+ imageBuilder = new ImageBuilderImpl(imageLoader);
thread = new Thread(this);
thread.setDaemon(true);
}
-
+
+ /**
+ * 同期イメージビルダを取得する。
+ * @return
+ */
+ public ImageBuilder getImageBuilder() {
+ return imageBuilder;
+ }
+
/**
* スレッドの実行部.
*/
lock.notifyAll();
}
// リクエストを処理する.
- AsyncImageBuilder.super.requestJob(job);
-
+ imageBuilder.requestJob(job);
+
} catch (InterruptedException ex) {
logger.log(Level.FINE, "AsyncImageBuilder thead interrupted.");
// 割り込みされた場合、単にループを再開する.
-
+
} catch (Exception ex) {
logger.log(Level.SEVERE, "AsyncImageBuilder failed.", ex);
// ジョブ合成中の予期せぬ例外はログに記録するのみで
}
logger.log(Level.FINE, "AsyncImageBuilder thread stopped.");
}
-
+
/**
* イメージ作成ジョブをリクエストする.<br>
* イメージ作成ジョブは非同期に実行される.<br>
if (this.requestJob != null && this.requestJob instanceof AsyncImageBuildJob) {
((AsyncImageBuildJob) this.requestJob).onAbandoned();
}
-
+
// リクエストをセットして待機中のスレッドに通知を出す.
this.requestJob = imageSource;
if (imageSource != null && imageSource instanceof AsyncImageBuildJob) {
public boolean isAlive() {
return thread.isAlive();
}
-
+
/**
* スレッドを開始する.
*/
}
}
}
-
}
if (param == null) {
param = new ColorConvertParameter();
}
- collector.setImageSource(layer, layerOrder, imageResource, param);
+ String partsName = partsIdentifier.getPartsName();
+ collector.setImageSource(partsName, layer, layerOrder, imageResource, param);
}
});
collector.setComplite();
*
* @author seraphy
*/
-public class ImageBuilder {
-
- /**
- * 各パーツ情報の読み取りタイムアウト
- */
- private static final int MAX_TIMEOUT = 20; // Secs
+public interface ImageBuilder {
/**
* 各パーツ情報を設定するためのインターフェイス.<br>
* 複数パーツある場合は、これを繰り返し呼び出す.<br>
* すべて呼び出したらsetCompliteを呼び出す.<br>
*
+ * @param partsName
+ * パーツ名
* @param layer
* レイヤー
* @param layerOrder
* @param param
* 色変換情報
*/
- void setImageSource(Layer layer, float layerOrder, ImageResource imageResource, ColorConvertParameter param);
+ void setImageSource(String partsName, Layer layer, float layerOrder, ImageResource imageResource, ColorConvertParameter param);
/**
* パーツの登録が完了したことを通知する。
void handleException(Throwable ex);
}
+ public interface ImageBuildJob2 extends ImageBuildJob {
+
+ void onCreateLayerImage(String partsName, Layer layer, BufferedImage img);
+ }
+
+ boolean requestJob(final ImageBuildJob imageBuildJob);
+}
+
+class ImageBuilderImpl implements ImageBuilder {
+
+ /**
+ * 各パーツ情報の読み取りタイムアウト
+ */
+ private static final int MAX_TIMEOUT = 20; // Secs
+
/**
* イメージ構築に使用したパーツ情報
*
private final ImageBuildPartsInfo partsInfo;
- private final long lastModified;
+ private final LoadedImage loadedImage;
public BuildedPartsInfo(ImageBuildPartsInfo partsInfo, LoadedImage loadedImage) {
this.partsInfo = partsInfo;
- this.lastModified = loadedImage.getLastModified();
+ this.loadedImage = loadedImage;
}
public ImageBuildPartsInfo getPartsInfo() {
return partsInfo;
}
+ public LoadedImage getLoadedImage() {
+ return loadedImage;
+ }
+
public long getLastModified() {
- return lastModified;
+ return loadedImage.getLastModified();
}
}
}
/**
+ * イメージに構築したパーツ情報を取得する。
+ * 構築順序で返される。
+ * @return パーツ情報と構築されたレイヤーイメージ
+ */
+ public List<BuildedPartsInfo> getBuildPartsInfos() {
+ return buildPartsInfos;
+ }
+
+ /**
* イメージ構築結果を取得する.
*
* @return イメージ構築結果
* @param imageLoader
* イメージローダー
*/
- public ImageBuilder(ColorConvertedImageCachedLoader imageLoader) {
+ public ImageBuilderImpl(ColorConvertedImageCachedLoader imageLoader) {
if (imageLoader == null) {
throw new IllegalArgumentException();
}
// loadPartsが非同期に行われる場合、すぐに制御を戻す.
imageBuildJob.loadParts(new ImageSourceCollector() {
// ジョブリクエスト側よりイメージサイズの設定として呼び出される
+ @Override
public void setSize(Dimension size) {
synchronized (imageBuildInfo) {
imageBuildInfo.setRect(size.width, size.height);
}
}
+ @Override
public void setImageBgColor(Color color) {
synchronized (imageBuildInfo) {
imageBuildInfo.setImageBgColor(color);
}
}
+ @Override
public void setAffineTramsform(double[] param) {
if (param != null && !(param.length == 4 || param.length == 6)) {
throw new IllegalArgumentException("affineTransformParameter invalid length.");
}
}
// ジョブリクエスト側よりパーツの登録として呼び出される
- public void setImageSource(Layer layer, float layerOrder, ImageResource imageResource,
+ @Override
+ public void setImageSource(String partsName, Layer layer, float layerOrder, ImageResource imageResource,
ColorConvertParameter param) {
synchronized (imageBuildInfo) {
imageBuildInfo.add(new ImageBuildPartsInfo(
- imageBuildInfo.getPartsCount(), layer, layerOrder, imageResource, param));
+ partsName, imageBuildInfo.getPartsCount(), layer, layerOrder, imageResource, param));
}
}
// ジョブリクエスト側よりイメージサイズとパーツの登録が完了したことを通知される.
+ @Override
public void setComplite() {
compliteLock.release();
}
* イメージを構築するジョブ
* @return 画像がただちに得られた場合はtrue、そうでなければfalse
*/
+ @Override
public boolean requestJob(final ImageBuildJob imageBuildJob) {
if (imageBuildJob == null) {
throw new IllegalArgumentException();
lastUsedImageBuildInfo = imageBuildInfo;
}
+ // 構築したレイヤーごとの画像を通知する
+ if (imageBuildJob instanceof ImageBuildJob2) {
+ ImageBuildJob2 callback = (ImageBuildJob2) imageBuildJob;
+ for (BuildedPartsInfo bpInfo : lastUsedImageBuildInfo.getBuildPartsInfos()) {
+ ImageBuildPartsInfo partsInfo = bpInfo.getPartsInfo();
+ String partsName = partsInfo.getPartsName();
+ Layer layer = partsInfo.getLayer();
+ LoadedImage loadedImage = bpInfo.getLoadedImage();
+ BufferedImage img = loadedImage.getImage();
+ callback.onCreateLayerImage(partsName, layer, img);
+ }
+ }
+
// 完成したカンバスを合成結果として通知する.
imageBuildJob.buildImage(new ImageOutput() {
public BufferedImage getImageOutput() {
final class ImageBuildPartsInfo implements Comparable<ImageBuildPartsInfo> {
/**
+ * パーツ名
+ */
+ private String partsName;
+
+ /**
* 定義順
*/
private int order;
private ColorConvertParameter colorParam;
- public ImageBuildPartsInfo(int order, Layer layer, float layerOrder, ImageResource imageResource,
+ public ImageBuildPartsInfo(String partsName, int order, Layer layer, float layerOrder, ImageResource imageResource,
ColorConvertParameter colorParam) {
+ this.partsName = partsName;
this.order = order;
this.layer = layer;
this.layerOrder = layerOrder;
@Override
public int hashCode() {
- return order ^ layer.hashCode() ^ imageResource.hashCode();
+ return partsName.hashCode() ^ order ^ layer.hashCode() ^ imageResource.hashCode();
}
@Override
}
if (obj != null && obj instanceof ImageBuildPartsInfo) {
ImageBuildPartsInfo o = (ImageBuildPartsInfo) obj;
- return order == o.order && layer.equals(o.layer)
+ return order == o.order && partsName.equals(o.partsName) && layer.equals(o.layer)
&& imageResource.equals(o.imageResource) && colorParam.equals(o.colorParam);
}
return false;
return ret;
}
+ public String getPartsName() {
+ return partsName;
+ }
+
public int getOrder() {
return order;
}
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SpinnerNumberModel;
import javax.swing.filechooser.FileFilter;
+import org.apache.tools.zip.ZipEntry;
+import org.apache.tools.zip.ZipOutputStream;
+
import charactermanaj.graphics.io.OutputOption.PictureMode;
import charactermanaj.graphics.io.OutputOption.ZoomRenderingType;
+import charactermanaj.model.Layer;
import charactermanaj.util.LocalizedMessageComboBoxRender;
import charactermanaj.util.LocalizedResourcePropertyLoader;
* イメージを保存するためのヘルパークラス.<br>
*/
public class ImageSaveHelper {
-
+
/**
* ロガー
*/
return isSupported(f);
}
-
+
protected boolean isSupported(File f) {
// サポートしている拡張子のいずれかにマッチするか?
// (大文字・小文字は区別しない.)
/**
* 現在の選択されたファイル名を取得し、そのファイル名がデフォルトの拡張子で終端していなければ
- * デフォルトの拡張子を設定してファイルチューザに設定し直す.<Br>
+ * デフォルトの拡張子を設定してファイルチューザに設定し直す.<Br>
* @param fileChooser ファイルチューザ
* @return デフォルトの拡張子で終端されたファイル
*/
if (outFile == null) {
return null;
}
-
+
if ( !isSupported(outFile)) {
String extName = "." + getSupprotedExtension()[0];
outFile = new File(outFile.getPath() + extName);
}
return outFile;
}
-
+
/**
* サポートするファイルの拡張子を取得する.<br>
* 最初のものがデフォルトの拡張子として用いられる.<br>
*/
protected abstract String[] getSupprotedExtension();
}
-
+
/**
* PNGファイルフィルタ
*/
return new String[] {"bmp"};
}
};
-
+
+ /**
+ * ZIPファイルフィルタ
+ */
+ protected static final FileFilter zipFilter = new ImageSaveHelperFilter() {
+ @Override
+ public String getDescription() {
+ return "Zip Archive(*.zip)";
+ }
+ @Override
+ protected String[] getSupprotedExtension() {
+ return new String[] {"zip"};
+ }
+ };
+
+ /**
+ * PSDファイルフィルタ
+ */
+ protected static final FileFilter psdFilter = new ImageSaveHelperFilter() {
+ @Override
+ public String getDescription() {
+ return "Adobe Photoshop(*.psd)";
+ }
+ @Override
+ protected String[] getSupprotedExtension() {
+ return new String[] {"psd"};
+ }
+ };
+
/**
* このヘルパクラスで定義されているファイルフィルタのリスト
*/
protected static final List<FileFilter> fileFilters = Arrays.asList(
- pngFilter, jpegFilter, bmpFilter);
-
+ pngFilter, jpegFilter, bmpFilter, psdFilter, zipFilter);
+
/**
* イメージビルダファクトリ
*/
* 未使用であれば規定値.<br>
*/
protected OutputOption outputOption;
-
+
/**
* 最後に使用したディレクトリ
*/
protected File lastUseSaveDir;
-
+
/**
* 最後に使用したフィルタ
*/
protected FileFilter lastUseFilter = pngFilter;
-
+
/**
* 最後に使用したディレクトリを設定する
* @param lastUseSaveDir 最後に使用したディレクトリ、設定しない場合はnull
public void setLastUseSaveDir(File lastUseSaveDir) {
this.lastUseSaveDir = lastUseSaveDir;
}
-
+
/**
* 最後に使用したディレクトリを取得する
* @return 最後に使用したディレクトリ、なければnull
public File getLastUsedSaveDir() {
return lastUseSaveDir;
}
-
+
/**
* コンストラクタ
if (selfilter instanceof ImageSaveHelperFilter) {
outFile = ((ImageSaveHelperFilter) selfilter).supplyDefaultExtension(this);
}
-
+
// ファイルが存在すれば上書き確認する.
if (outFile.exists()) {
Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
return;
}
}
-
+
super.approveSelection();
}
};
-
+
// // アクセサリパネルの追加˙
// final OutputOptionPanel accessoryPanel = new OutputOptionPanel(this.outputOption);
// fileChooser.setAccessory(accessoryPanel);
-
+
// ファイルフィルタ設定
fileChooser.setAcceptAllFileFilterUsed(false);
for (FileFilter fileFilter : fileFilters) {
if (ret != JFileChooser.APPROVE_OPTION) {
return null;
}
-
+
// // 出力オプションの保存
// OutputOption outputOption = accessoryPanel.getOutputOption();
// this.outputOption = outputOption;
File outFile = fileChooser.getSelectedFile();
lastUseSaveDir = outFile.getParentFile();
lastUseFilter = fileChooser.getFileFilter();
-
+
// 選択したファイルを返す.
- return outFile;
+ return outFile;
}
-
+
public OutputOption getOutputOption() {
return outputOption.clone();
}
-
+
public void setOutputOption(OutputOption outputOption) {
if (outputOption == null) {
throw new IllegalArgumentException();
}
/**
+ * ファイルから拡張子を取得する。
+ * @param outFile ファイル名
+ * @return 拡張子
+ * @throws IOException 拡張子がない場合
+ */
+ public String getFileExtension(File outFile) throws IOException {
+ // ファイル名から拡張子を取り出します.
+ String fname = outFile.getName();
+ int extpos = fname.lastIndexOf(".");
+ if (extpos < 0) {
+ throw new IOException("missing file extension.");
+ }
+ return fname.substring(extpos + 1).toLowerCase();
+ }
+
+ /**
* ファイル名を指定してイメージをファイルに出力します.<br>
* 出力形式は拡張子より判定します.<br>
* サポートされていない拡張子の場合はIOException例外が発生します.<br>
if (img == null || outFile == null) {
throw new IllegalArgumentException();
}
-
- // ファイル名から拡張子を取り出します.
- String fname = outFile.getName();
- int extpos = fname.lastIndexOf(".");
- if (extpos < 0) {
- throw new IOException("missing file extension.");
- }
- String ext = fname.substring(extpos + 1).toLowerCase();
+
+ String ext = getFileExtension(outFile);
// 拡張子に対するImageIOのライタを取得します.
Iterator<ImageWriter> ite = ImageIO.getImageWritersBySuffix(ext);
if (!ite.hasNext()) {
throw new IOException("unsupported file extension: " + ext);
}
-
+
ImageWriter iw = ite.next();
-
+
// ライタを使いイメージを書き込みます.
savePicture(img, imgBgColor, iw, outFile, warnings);
}
-
+
/**
* イメージをMIMEで指定された形式で出力します.
* @param img イメージ
savePicture(img, imgBgColor, iw, outstm, warnings);
outstm.flush();
}
-
+
protected void savePicture(BufferedImage img, Color imgBgColor, ImageWriter iw,
Object output, final StringBuilder warnings)
throws IOException {
iw.dispose();
}
}
-
+
/**
* ARGB形式から、アルファチャンネルを削除し、かわりに背景色を設定したBGR形式画像を返します.<br>
* JPEG画像として用いることを想定しています.<br>
}
return tmpImg;
}
+
+ public static class LayerImage {
+
+ private String partsName;
+
+ private Layer layer;
+
+ private BufferedImage image;
+
+ public LayerImage(String partsName, Layer layer, BufferedImage image) {
+ super();
+ this.partsName = partsName;
+ this.layer = layer;
+ this.image = image;
+ }
+
+ public String getPartsName() {
+ return partsName;
+ }
+
+ public Layer getLayer() {
+ return layer;
+ }
+
+ public BufferedImage getImage() {
+ return image;
+ }
+
+ @Override
+ public String toString() {
+ return "(partsName=" + partsName + ", layer=" + layer + ")";
+ }
+ }
+
+ /**
+ * zipにレイヤーごとの画像とプレビュー画像をまとめて保存する
+ * @param outFile
+ * @param layerImages
+ * @param compositeImg
+ * @throws IOException
+ */
+ public void saveToZip(File outFile, Collection<LayerImage> layerImages, BufferedImage compositeImg)
+ throws IOException {
+ ZipOutputStream zos = new ZipOutputStream(outFile);
+ try {
+ if (layerImages != null) {
+ for (LayerImage layerImage : layerImages) {
+ String partsName = layerImage.getPartsName();
+ Layer layer = layerImage.getLayer();
+ String dir = layer.getDir();
+ String fname = dir + "/" + partsName + ".png";
+
+ ZipEntry zipEntry = new ZipEntry(fname);
+ zos.putNextEntry(zipEntry);
+ ImageIO.write(layerImage.getImage(), "png", zos);
+ zos.closeEntry();
+ }
+ }
+ if (compositeImg != null) {
+ ZipEntry zipEntry = new ZipEntry("preview.png");
+ zos.putNextEntry(zipEntry);
+ ImageIO.write(compositeImg, "png", zos);
+ zos.closeEntry();
+ }
+
+ } finally {
+ zos.close();
+ }
+ }
+
+ /**
+ * PSD形式で保存する
+ * @param outFile
+ * @param layerImages
+ * @param compositeImg
+ * @throws IOException
+ */
+ public void saveToPSD(File outFile, Collection<LayerImage> layerImages, BufferedImage compositeImg)
+ throws IOException {
+ List<PSDCreator.LayerData> layerDatas = new ArrayList<PSDCreator.LayerData>();
+ Map<String, Integer> dupchk = new HashMap<String, Integer>();
+ for (LayerImage layerImage : layerImages) {
+ Layer layer = layerImage.getLayer();
+ String layerName = layer.getDir();
+ Integer cnt = dupchk.get(layerName);
+ if (cnt == null) {
+ cnt = 1;
+ } else {
+ cnt = cnt + 1;
+ layerName = layerName + "(" + cnt + ")";
+ }
+ dupchk.put(layerName, cnt);
+
+ BufferedImage img = layerImage.getImage();
+ PSDCreator.LayerData layerData = new PSDCreator.LayerData(layerName, img);
+ layerDatas.add(layerData);
+ }
+ byte[] psdContents = PSDCreator.createPSD(layerDatas);
+
+ FileOutputStream fos = new FileOutputStream(outFile);
+ try {
+ FileChannel fc = fos.getChannel();
+ try {
+ ByteBuffer buf = ByteBuffer.wrap(psdContents);
+ while (buf.hasRemaining()) {
+ fc.write(buf);
+ }
+
+ } finally {
+ fc.close();
+ }
+ } finally {
+ fos.close();
+ }
+ }
}
/**
private static final long serialVersionUID = 1L;
private JSpinner jpegQualitySpinner;
-
+
private JCheckBox lblZoom;
-
+
private JSpinner zoomSpinner;
-
+
private JComboBox zoomAlgoCombo;
-
+
private JComboBox pictureModeCombo;
-
+
private JCheckBox checkForceBgColor;
-
+
public OutputOptionPanel() {
this(new OutputOption());
}
gbc.gridwidth = 1;
gbc.weightx = 0.;
add(Box.createHorizontalStrut(6), gbc);
-
+
// JPEG
gbc.gridx = 0;
gbc.gridy = 0;
strings.getProperty("outputOption.jpeg.caption"));
lblJpeg.setFont(lblJpeg.getFont().deriveFont(Font.BOLD));
add(lblJpeg, gbc);
-
+
gbc.gridx = 1;
gbc.gridy = 1;
gbc.gridwidth = 1;
add(new JLabel(
strings.getProperty("outputOption.jpeg.quality"),
JLabel.RIGHT), gbc);
-
+
SpinnerNumberModel spmodel = new SpinnerNumberModel(100, 10, 100, 1);
this.jpegQualitySpinner = new JSpinner(spmodel);
-
+
gbc.gridx = 2;
gbc.gridy = 1;
gbc.gridwidth = 1;
lblZoom = new JCheckBox(strings.getProperty("outputOption.zoom.caption"));
lblZoom.setFont(lblJpeg.getFont().deriveFont(Font.BOLD));
add(lblZoom, gbc);
-
+
gbc.gridx = 1;
gbc.gridy = 3;
gbc.gridwidth = 1;
gbc.insets = new Insets(3, 3, 3, 3);
add(new JLabel(
strings.getProperty("outputOption.zoom.factor"), JLabel.RIGHT), gbc);
-
+
SpinnerNumberModel zoomSpModel = new SpinnerNumberModel(100, 20, 800, 1);
this.zoomSpinner = new JSpinner(zoomSpModel);
-
+
gbc.gridx = 2;
gbc.gridy = 3;
gbc.gridwidth = 1;
add(zoomSpinner, gbc);
-
+
gbc.gridx = 3;
gbc.gridy = 3;
gbc.gridwidth = 1;
add(new JLabel(
strings.getProperty("outputOption.zoom.renderingMode"),
JLabel.RIGHT), gbc);
-
+
this.zoomAlgoCombo = new JComboBox(ZoomRenderingType.values());
this.zoomAlgoCombo.setRenderer(new LocalizedMessageComboBoxRender(strings));
gbc.gridx = 2;
gbc.gridwidth = 2;
gbc.weightx = 0.;
add(zoomAlgoCombo, gbc);
-
+
// 画像モード
gbc.gridx = 0;
gbc.gridy = 5;
strings.getProperty("outputOption.picture"));
lblPictureMode.setFont(lblJpeg.getFont().deriveFont(Font.BOLD));
add(lblPictureMode, gbc);
-
+
gbc.gridx = 1;
gbc.gridy = 6;
gbc.gridwidth = 1;
add(new JLabel(
strings.getProperty("outputOption.picture.type"),
JLabel.RIGHT), gbc);
-
+
this.pictureModeCombo = new JComboBox(PictureMode.values());
this.pictureModeCombo.setRenderer(
new LocalizedMessageComboBoxRender(strings));
gbc.gridy = 6;
gbc.gridwidth = 2;
gbc.weightx = 1.;
- add(pictureModeCombo, gbc);
-
+ add(pictureModeCombo, gbc);
+
gbc.gridx = 1;
gbc.gridy = 7;
gbc.gridwidth = 3;
gbc.weightx = 0.;
checkForceBgColor = new JCheckBox(
- strings.getProperty("outputOption.picture.forceBgColor"));
+ strings.getProperty("outputOption.picture.forceBgColor"));
add(checkForceBgColor, gbc);
-
+
JPanel pnlBgAlpha = new JPanel(new BorderLayout(3, 3));
pnlBgAlpha.add(new JLabel("背景アルファ"), BorderLayout.WEST);
-
+
SpinnerNumberModel bgAlphaModel = new SpinnerNumberModel(255, 0, 255, 1);
JSpinner bgAlphaSpinner = new JSpinner(bgAlphaModel);
pnlBgAlpha.add(bgAlphaSpinner, BorderLayout.CENTER);
-
+
gbc.gridx = 1;
gbc.gridy = 8;
gbc.gridwidth = 3;
gbc.weightx = 1.;
gbc.weighty = 1.;
add(Box.createGlue(), gbc);
-
+
// update
setOutputOption(outputOption);
}
-
+
public void setOutputOption(OutputOption outputOption) {
if (outputOption == null) {
outputOption = new OutputOption();
}
-
+
jpegQualitySpinner.setValue((int) (outputOption.getJpegQuality() * 100));
lblZoom.setSelected(outputOption.isEnableZoom());
zoomSpinner.setValue((int) (outputOption.getZoomFactor() * 100));
outputOption.setZoomRenderingType((ZoomRenderingType) zoomAlgoCombo.getSelectedItem());
outputOption.setPictureMode((PictureMode) pictureModeCombo.getSelectedItem());
outputOption.setForceBgColor(checkForceBgColor.isSelected());
-
+
return outputOption;
}
}
--- /dev/null
+package charactermanaj.graphics.io;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.awt.image.WritableRaster;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * 複数レイヤー画像をPSD形式のデータとして作成する。
+ * https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/
+ *
+ * @author seraphy
+ */
+public final class PSDCreator {
+
+ /**
+ * レイヤーデータ
+ */
+ public static class LayerData {
+
+ /**
+ * レイヤー名
+ */
+ private String layerName;
+
+ /**
+ * レイヤーの画像(TYPE_INT_ARGB限定)
+ */
+ private BufferedImage image;
+
+ public LayerData(String layerName, BufferedImage image) {
+ this.layerName = layerName;
+ this.image = image;
+ }
+
+ public String getLayerName() {
+ return layerName;
+ }
+
+ public BufferedImage getImage() {
+ return image;
+ }
+ }
+
+ /**
+ * プライベートコンストラクタ
+ */
+ private PSDCreator() {
+ super();
+ }
+
+ /**
+ * レイヤーを指定してPSDデータを作成する
+ * @param layerDatas レイヤーのコレクション、順番に重ねられる
+ * @return PSDデータ
+ * @throws IOException
+ */
+ public static byte[] createPSD(Collection<LayerData> layerDatas) throws IOException {
+ if (layerDatas == null) {
+ throw new NullPointerException("layerDatas is required.");
+ }
+ if (layerDatas.isEmpty()) {
+ throw new IllegalArgumentException("layerDatas must not be empty.");
+ }
+
+ BufferedImage cimg = createCompositeImage(layerDatas);
+ int width = cimg.getWidth();
+ int height = cimg.getHeight();
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+
+ dos.write("8BPS".getBytes());
+ dos.writeShort(1);
+ dos.write(new byte[6]); // reserved 6bytes
+
+ dos.writeShort(4); // argb
+
+ dos.writeInt(height);
+ dos.writeInt(width);
+
+ int depth = 8;
+ dos.writeShort(depth);
+
+ dos.writeShort(3); // ColorMode=RGB(3)
+
+ dos.writeInt(0); // カラーモードセクションなし
+ dos.writeInt(0); // リソースセクションなし
+
+ // レイヤーマスクセクション
+ byte[] layerMaskSection = createLayerMaskSection(layerDatas);
+ dos.writeInt(layerMaskSection.length);
+ dos.write(layerMaskSection);
+
+ // 画像セクション
+ dos.writeShort(0); // RAW
+ byte[][] channelDatas = createChannels(cimg);
+ int[] channelMap = { 1, 2, 3, 0 }; // R, G, B, Aにマップ
+ for (int channel = 0; channel < channelMap.length; channel++) {
+ byte[] channelData = channelDatas[channelMap[channel]];
+ dos.write(channelData);
+ }
+
+ return bos.toByteArray();
+ }
+
+ /**
+ * レイヤーマスクセクションを作成する
+ * @param layerDatas
+ * @return
+ * @throws IOException
+ */
+ private static byte[] createLayerMaskSection(Collection<LayerData> layerDatas) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+
+ byte[] layerData = createLayerData(layerDatas);
+ dos.writeInt(layerData.length);
+ dos.write(layerData);
+
+ return bos.toByteArray();
+ }
+
+ /**
+ * レイヤーデータの作成
+ * @param layerDatas
+ * @return
+ * @throws IOException
+ */
+ private static byte[] createLayerData(Collection<LayerData> layerDatas) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+
+ int numOfLayers = layerDatas.size();
+ dos.writeShort(numOfLayers); // non pre-multiplied
+
+ short[] channels = { -1, 0, 1, 2 }; // ALPHA, RED, GREEN, BLUE
+
+ for (LayerData layerData : layerDatas) {
+ String layerName = layerData.getLayerName();
+ BufferedImage image = layerData.getImage();
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ dos.writeInt(0); // top
+ dos.writeInt(0); // left
+ dos.writeInt(height); // bottom
+ dos.writeInt(width); // right
+
+ dos.writeShort(channels.length);
+
+ int rawSize = width * height;
+ for (int channel = 0; channel < channels.length; channel++) {
+ dos.writeShort(channels[channel]);
+ dos.writeInt(rawSize);
+ }
+
+ dos.write("8BIM".getBytes());
+ dos.write("norm".getBytes());
+
+ dos.write((byte) 255); // opacity
+ dos.write((byte) 0); // clipping
+ dos.write((byte) 0); // protection
+ dos.write((byte) 0); // filler
+
+ byte[] layerMaskData = createLayerMaskData();
+ byte[] layerBlendingData = createLayerBlendingData();
+ byte[] layerNameData = createLayerName(layerName);
+ int lenOfAdditional = layerMaskData.length + layerBlendingData.length + layerNameData.length;
+
+ dos.writeInt(lenOfAdditional);
+ dos.write(layerMaskData);
+ dos.write(layerBlendingData);
+ dos.write(layerNameData);
+ }
+
+ for (LayerData layerData : layerDatas) {
+ BufferedImage image = layerData.getImage();
+
+ byte[][] channelsData = createChannels(image);
+
+ for (int channel = 0; channel < channels.length; channel++) {
+ dos.writeShort(0); // RAW
+
+ byte[] channelData = channelsData[channel];
+ dos.write(channelData);
+ }
+ }
+
+ return bos.toByteArray();
+ }
+
+ /**
+ * 空のレイヤーマスクデータ作成
+ * @return
+ * @throws IOException
+ */
+ private static byte[] createLayerMaskData() throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+ dos.writeInt(0);
+ return bos.toByteArray();
+ }
+
+ /**
+ * 空のレイヤーブレンダーデータの作成
+ * @return
+ * @throws IOException
+ */
+ private static byte[] createLayerBlendingData() throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+ dos.writeInt(0);
+ return bos.toByteArray();
+ }
+
+ /**
+ * レイヤー名の作成
+ * @param layerName
+ * @return
+ * @throws IOException
+ */
+ private static byte[] createLayerName(String layerName) throws IOException {
+ byte[] nameBuf = layerName.getBytes("UTF-8");
+ int layerNameSize = 1 + nameBuf.length; // PASCAL文字列長 (16の倍数サイズ)
+ int blockSize = (layerNameSize / 4) * 4 + ((layerNameSize % 4 > 0) ? 4 : 0);
+ int paddingSize = blockSize - layerNameSize;
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+ dos.write((byte) nameBuf.length);
+ dos.write(nameBuf);
+ dos.write(new byte[paddingSize]);
+ return bos.toByteArray();
+ }
+
+ /**
+ * 32ビットARGB形式のBuffeedImageを受け取り、
+ * ARGBのbyte[][]配列に変換して返す。
+ * @param img イメージ
+ * @return チャネル別配列
+ */
+ private static byte[][] createChannels(BufferedImage img) {
+ WritableRaster raster = img.getRaster();
+ DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer();
+ int[] pixels = buffer.getData();
+
+ int width = img.getWidth();
+ int height = img.getHeight();
+ int mx = width * height;
+ byte[][] channels = new byte[4][mx];
+ for (int idx = 0; idx < mx; idx++) {
+ int argb = pixels[idx];
+
+ int alpha = (argb >> 24) & 0xff;
+ int red = (argb >> 16) & 0xff;
+ int green = (argb >> 8) & 0xff;
+ int blue = argb & 0xff;
+
+ channels[0][idx] = (byte) alpha;
+ channels[1][idx] = (byte) red;
+ channels[2][idx] = (byte) green;
+ channels[3][idx] = (byte) blue;
+ }
+
+ return channels;
+ }
+
+ /**
+ * レイヤーコレクションを重ねて1つの画像にして返す
+ * @param layerDatas レイヤーコレクション
+ * @return 重ね合わせた画像
+ */
+ private static BufferedImage createCompositeImage(Collection<LayerData> layerDatas) {
+ int width = 0;
+ int height = 0;
+ for (LayerData layerData : layerDatas) {
+ BufferedImage img = layerData.getImage();
+ int w = img.getWidth();
+ int h = img.getHeight();
+ if (w > width) {
+ width = w;
+ }
+ if (h > height) {
+ height = h;
+ }
+ }
+
+ BufferedImage cimg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g = cimg.createGraphics();
+ try {
+ for (LayerData layerData : layerDatas) {
+ BufferedImage img = layerData.getImage();
+ int w = img.getWidth();
+ int h = img.getHeight();
+ g.drawImage(img, 0, 0, w, h, 0, 0, w, h, null);
+ }
+ } finally {
+ g.dispose();
+ }
+ return cimg;
+ }
+}
import charactermanaj.graphics.AsyncImageBuilder;
import charactermanaj.graphics.ColorConvertedImageCachedLoader;
import charactermanaj.graphics.ImageBuildJobAbstractAdaptor;
+import charactermanaj.graphics.ImageBuilder;
import charactermanaj.graphics.ImageBuilder.ImageOutput;
+import charactermanaj.graphics.ImageBuilder.ImageSourceCollector;
+import charactermanaj.graphics.filters.ColorConvertParameter;
+import charactermanaj.graphics.io.ImageResource;
import charactermanaj.graphics.io.ImageSaveHelper;
+import charactermanaj.graphics.io.ImageSaveHelper.LayerImage;
import charactermanaj.graphics.io.OutputOption;
import charactermanaj.graphics.io.UkagakaImageSaveHelper;
import charactermanaj.model.AppConfig;
import charactermanaj.model.ColorGroup;
import charactermanaj.model.CustomLayerOrder;
import charactermanaj.model.CustomLayerOrderKey;
+import charactermanaj.model.Layer;
import charactermanaj.model.LayerOrderMapper;
import charactermanaj.model.PartsCategory;
import charactermanaj.model.PartsColorInfo;
import charactermanaj.model.io.CharacterDataPersistent;
import charactermanaj.model.io.CustomLayerOrderPersist;
import charactermanaj.model.io.CustomLayerOrderPersist.CustomLayerOrderPersistListener;
+import charactermanaj.model.io.PartsImageCollectionParser;
import charactermanaj.model.io.PartsImageDirectoryWatchAgent;
import charactermanaj.model.io.PartsImageDirectoryWatchAgentFactory;
import charactermanaj.model.io.PartsImageDirectoryWatchEvent;
imageSaveHelper.setOutputOption(outputOption);
// ファイルダイアログ表示
- File outFile = imageSaveHelper.showSaveFileDialog(this);
+ final File outFile = imageSaveHelper.showSaveFileDialog(this);
if (outFile == null) {
return;
}
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
- imageSaveHelper.savePicture(img, imgBgColor, outFile, warnings);
+ String ext = imageSaveHelper.getFileExtension(outFile);
+ if ("zip".equals(ext)) {
+ saveLayers(outFile, new SaveLayerCallback() {
+ @Override
+ public void collect(List<LayerImage> layerImages, BufferedImage compositeImg)
+ throws IOException {
+ imageSaveHelper.saveToZip(outFile, layerImages, compositeImg);
+ }
+ });
+ } else if ("psd".equals(ext)) {
+ saveLayers(outFile, new SaveLayerCallback() {
+ @Override
+ public void collect(List<LayerImage> layerImages, BufferedImage compositeImg)
+ throws IOException {
+ imageSaveHelper.saveToPSD(outFile, layerImages, compositeImg);
+ }
+ });
+ } else {
+ imageSaveHelper.savePicture(img, imgBgColor, outFile, warnings);
+ }
} finally {
setCursor(Cursor.getDefaultCursor());
}
}
+ private interface SaveLayerCallback {
+ void collect(List<ImageSaveHelper.LayerImage> layerImages, BufferedImage compositeImg) throws IOException;
+ }
+
+ private void saveLayers(final File outFile, SaveLayerCallback callback) throws IOException {
+ final PartsImageCollectionParser partsImageCollectorParser = new PartsImageCollectionParser(characterData);
+ final List<ImageSaveHelper.LayerImage> layerImages = new ArrayList<ImageSaveHelper.LayerImage>();
+ final BufferedImage[] result = new BufferedImage[1];
+ ImageBuilder syncImgBuilder = this.imageBuilder.getImageBuilder(); // 同期型のイメージビルダを取得する
+ syncImgBuilder.requestJob(new ImageBuilder.ImageBuildJob2() {
+ @Override
+ public void loadParts(final ImageSourceCollector collector) throws IOException {
+ PartsSet partsSet = partsSelectionManager.createPartsSet();
+ partsSet.getActiveCustomLayerPatternIds();
+ LayerOrderMapper layerOrderMapper = customLayerPatternMgr.getLayerOrderMapper();
+ collector.setSize(partsImageCollectorParser.getPartsSpecResolver().getImageSize());
+ collector.setImageBgColor(partsSet.getBgColor());
+ collector.setAffineTramsform(partsSet.getAffineTransformParameter());
+ partsImageCollectorParser.parse(partsSet, layerOrderMapper,
+ new PartsImageCollectionParser.PartsImageCollectionHandler() {
+ public void detectImageSource(PartsIdentifier partsIdentifier,
+ Layer layer, float layerOrder, ImageResource imageResource,
+ ColorConvertParameter param) {
+ if (param == null) {
+ param = new ColorConvertParameter();
+ }
+ String partsName = partsIdentifier.getPartsName();
+ collector.setImageSource(partsName, layer, layerOrder, imageResource, param);
+ }
+ });
+ collector.setComplite();
+ }
+
+ @Override
+ public void handleException(Throwable ex) {
+ ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);
+ }
+
+ @Override
+ public void buildImage(ImageOutput output) {
+ result[0] = output.getImageOutput();
+ }
+
+ @Override
+ public void onCreateLayerImage(String partsName, Layer layer, BufferedImage img) {
+ layerImages.add(new ImageSaveHelper.LayerImage(partsName,layer, img));
+ }
+ });
+
+ // 集まった各レイヤーの画像と、合成された画像を呼び出し元にコールバックする
+ callback.collect(layerImages, result[0]);
+ }
+
/**
* 伺か用PNG/PNAの出力.
*/