KDP(電子出版)のメモ 急急如律令

Amazon Kindleダイレクト・パブリッシングでの電子出版や電子書籍の作成販売について、文章やイラストの作成や編集方法について書いています。

画像処理部分をAndroidではBufferedImageが使えないので、Bitmap に変更する。

画像処理部分をAndroidではBufferedImageが使えないので、Bitmap に変更する。

chatgptに「次のコードAndroid向けに書き換えて」 ImageInfoReader.java

/** 指定した順番の画像情報を取得 
     * @throws RarException */
    public BufferedImage getImage(int idx) throws IOException, RarException
    {
        return this.getImage(this.imageFileNames.get(idx));
    }
    
    
    /** ファイル名から画像を取得
     * 拡張子変更等は外側で修正しておく
     * ファイルシステムまたはZipファイルから指定されたファイル名の画像を取得
     * @param srcImageFileName ファイル名 Zipならエントリ名
     * ※先頭からシークされるので遅い? 
     * @throws RarException */
    public BufferedImage getImage(String srcImageFileName) throws IOException, RarException
    {
        if (this.isFile) {
            File file = new File(this.srcParentPath+srcImageFileName);
            if (!file.exists()) {
                //拡張子修正
                srcImageFileName = this.correctExt(srcImageFileName);
                file = new File(this.srcParentPath+srcImageFileName);
                if (!file.exists()) return null;
            }
            try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file), 8192)) {
                return ImageUtils.readImage(srcImageFileName.substring(srcImageFileName.lastIndexOf('.') + 1).toLowerCase(), bis);
            }
        } else {
            if (this.srcFile.getName().endsWith(".rar")) {
                InputStream is = null;
                Archive archive = new Archive(srcFile);
                try {
                FileHeader fileHeader = archive.nextFileHeader();
                while (fileHeader != null) {
                    if (!fileHeader.isDirectory()) {
                        String entryName = fileHeader.getFileName();
                        entryName = entryName.replace('\\', '/');
                        if (srcImageFileName.equals(entryName)) {
                            is = archive.getInputStream(fileHeader);
                            return ImageUtils.readImage(srcImageFileName.substring(srcImageFileName.lastIndexOf('.')+1).toLowerCase(), is);
                        }
                    }
                    fileHeader = archive.nextFileHeader();
                }
                } finally {
                    if (is != null) is.close();
                    archive.close();
                }
                
            } else {
                try (ZipFile zf = ZipFile.builder().setFile(this.srcFile).setUseUnicodeExtraFields(true).get()) {
                    ZipArchiveEntry entry = zf.getEntry(srcImageFileName);
                    if (entry == null) {
                        srcImageFileName = this.correctExt(srcImageFileName);
                        entry = zf.getEntry(srcImageFileName);
                        if (entry == null) return null;
                    }
                    try (InputStream is = zf.getInputStream(entry)) {
                        return ImageUtils.readImage(srcImageFileName.substring(srcImageFileName.lastIndexOf('.')+1).toLowerCase(), is);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

Android向けにリファクタリングする際、以下の点を考慮する必要があります。

BufferedImage の代替: Androidでは BufferedImage は使えないため、Bitmap に変更。 FileInputStream の扱い: Androidでは FileInputStream を BitmapFactory.decodeStream() で処理する。 Rar/Zipの処理: junrar や android-zip4j などのライブラリを使用することを検討。 ストリーム管理: try-with-resources を活用し、リソースリークを防ぐ。 以下がAndroid向けに書き換えたコードです。

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import com.github.junrar.Archive;
import com.github.junrar.rarfile.FileHeader;

public class ImageLoader {
    private String srcParentPath;
    private File srcFile;
    private boolean isFile;

    /** 指定した順番の画像情報を取得 */
    public Bitmap getImage(int idx) throws IOException {
        return this.getImage(this.imageFileNames.get(idx));
    }

    /** ファイル名から画像を取得 */
    public Bitmap getImage(String srcImageFileName) throws IOException {
        if (this.isFile) {
            File file = new File(this.srcParentPath + srcImageFileName);
            if (!file.exists()) {
                // 拡張子修正
                srcImageFileName = this.correctExt(srcImageFileName);
                file = new File(this.srcParentPath + srcImageFileName);
                if (!file.exists()) return null;
            }
            try (FileInputStream fis = new FileInputStream(file)) {
                return BitmapFactory.decodeStream(fis);
            }
        } else {
            if (this.srcFile.getName().endsWith(".rar")) {
                return getImageFromRar(srcImageFileName);
            } else {
                return getImageFromZip(srcImageFileName);
            }
        }
    }

    /** RARファイル内の画像を取得 */
    private Bitmap getImageFromRar(String srcImageFileName) throws IOException {
        try (Archive archive = new Archive(new FileInputStream(srcFile))) {
            FileHeader fileHeader = archive.nextFileHeader();
            while (fileHeader != null) {
                if (!fileHeader.isDirectory()) {
                    String entryName = fileHeader.getFileName().replace('\\', '/');
                    if (srcImageFileName.equals(entryName)) {
                        try (InputStream is = archive.getInputStream(fileHeader)) {
                            return BitmapFactory.decodeStream(is);
                        }
                    }
                }
                fileHeader = archive.nextFileHeader();
            }
        }
        return null;
    }

    /** ZIPファイル内の画像を取得 */
    private Bitmap getImageFromZip(String srcImageFileName) throws IOException {
        try (ZipFile zipFile = new ZipFile(this.srcFile)) {
            ZipEntry entry = zipFile.getEntry(srcImageFileName);
            if (entry == null) {
                srcImageFileName = this.correctExt(srcImageFileName);
                entry = zipFile.getEntry(srcImageFileName);
                if (entry == null) return null;
            }
            try (InputStream is = zipFile.getInputStream(entry)) {
                return BitmapFactory.decodeStream(is);
            }
        }
    }

    /** 拡張子を補正するメソッド (実装例) */
    private String correctExt(String fileName) {
        // 例: .jpeg を .jpg に変更など
        return fileName.replace(".jpeg", ".jpg").replace(".JPG", ".jpg");
    }
}

変更点・ポイント

BufferedImage を Bitmap に変更

BitmapFactory.decodeStream() を使用して InputStream から Bitmap を取得。

FileInputStream の適切な処理

try-with-resources を使用し、InputStream を適切に閉じる。

junrar を使ってRARファイルを処理

Archive クラスを使用して .rar のエントリを検索し、該当する InputStream から Bitmap を取得。

ZipFile を使ってZIPファイルを処理

ZipFile で ZipEntry を検索し、InputStream を取得して BitmapFactory.decodeStream() で画像をデコード。

このコードをAndroidアプリに組み込めば、RARやZIP内の画像も簡単に取得できます! 🎯

ログ表示をどうするか

次のコードAndroid向けに書き換えて

package io.github.kyukyunyorituryo.aozoraepub3.util;
import javax.swing.JTextArea;

/** ログ出力Wrapperクラス */
public class LogAppender
{
    static JTextArea jTextArea = null;
    
    static public void setTextArea(JTextArea _jTextArea)
    {
        jTextArea = _jTextArea;
    }
    
    static public void println(String log)
    {
        LogAppender.append(log);
        LogAppender.append("\n");
    }
    static public void println()
    {
        LogAppender.append("\n");
    }
    static public void append(String log)
    {
        if (jTextArea != null) {
            jTextArea.append(log);
            jTextArea.setCaretPosition(jTextArea.getDocument().getLength());
        }
        System.out.print(log);
    }
    
    static public void printStaclTrace(Exception e)
    {
        for (StackTraceElement ste : e.getStackTrace()) {
            LogAppender.append(ste.toString());
            LogAppender.append("\n");
        }
    }
    
    static public void msg(int lineNum, String msg, String desc)
    {
        LogAppender.append(msg);
        LogAppender.append(" ("+(lineNum+1)+")");
        if (desc != null) {
            LogAppender.append(" : ");
            LogAppender.append(desc);
        }
        LogAppender.append("\n");
    }
    
    static public void error(String msg)
    {
        LogAppender.append("[ERROR] ");
        LogAppender.append(msg);
        LogAppender.append("\n");
    }
    static public void error(int lineNum, String msg, String desc)
    {
        LogAppender.append("[ERROR] ");
        LogAppender.msg(lineNum, msg, desc);
    }
    static public void error(int lineNum, String msg)
    {
        LogAppender.append("[ERROR] ");
        LogAppender.msg(lineNum, msg, null);
    }
    
    static public void warn(int lineNum, String msg, String desc)
    {
        LogAppender.append("[WARN] ");
        LogAppender.msg(lineNum, msg, desc);
    }
    static public void warn(int lineNum, String msg)
    {
        LogAppender.append("[WARN] ");
        LogAppender.msg(lineNum, msg, null);
    }
    
    static public void info(int lineNum, String msg, String desc)
    {
        LogAppender.append("[INFO] ");
        LogAppender.msg(lineNum, msg, desc);
    }
    static public void info(int lineNum, String msg)
    {
        LogAppender.append("[INFO] ");
        LogAppender.msg(lineNum, msg, null);
    }
}

Android向けにリファクタリングしたコードは以下のようになります。JTextAreaの代わりにTextViewを使用し、スレッドの制約を考慮してHandlerを用いてUIを更新します。

修正点: JTextArea → TextView(Android UI向け) System.out.print(log); → Log.d("LogAppender", log);(AndroidのLogクラスを使用) TextViewの更新をメインスレッドで行うためにHandlerを追加

package io.github.kyukyunyorituryo.aozoraepub3.util;

import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.TextView;

/** ログ出力Wrapperクラス */
public class LogAppender {
    private static TextView textView = null;
    private static final Handler handler = new Handler(Looper.getMainLooper());

    public static void setTextView(TextView _textView) {
        textView = _textView;
    }

    public static void println(String log) {
        append(log);
        append("\n");
    }

    public static void println() {
        append("\n");
    }

    public static void append(String log) {
        Log.d("LogAppender", log);  // Androidのログ出力
        if (textView != null) {
            handler.post(() -> {
                textView.append(log);
                textView.append("\n");
            });
        }
    }

    public static void printStackTrace(Exception e) {
        for (StackTraceElement ste : e.getStackTrace()) {
            append(ste.toString());
            append("\n");
        }
    }

    public static void msg(int lineNum, String msg, String desc) {
        append(msg + " (" + (lineNum + 1) + ")");
        if (desc != null) {
            append(" : " + desc);
        }
        append("\n");
    }

    public static void error(String msg) {
        append("[ERROR] " + msg + "\n");
    }

    public static void error(int lineNum, String msg, String desc) {
        append("[ERROR] ");
        msg(lineNum, msg, desc);
    }

    public static void error(int lineNum, String msg) {
        append("[ERROR] ");
        msg(lineNum, msg, null);
    }

    public static void warn(int lineNum, String msg, String desc) {
        append("[WARN] ");
        msg(lineNum, msg, desc);
    }

    public static void warn(int lineNum, String msg) {
        append("[WARN] ");
        msg(lineNum, msg, null);
    }

    public static void info(int lineNum, String msg, String desc) {
        append("[INFO] ");
        msg(lineNum, msg, desc);
    }

    public static void info(int lineNum, String msg) {
        append("[INFO] ");
        msg(lineNum, msg, null);
    }
}
> ポイント
> Androidのログ機能
> System.out.print(log); の代わりに Log.d("LogAppender", log); を使用しました。
> 
> UI更新の考慮
> TextView の更新はメインスレッドで行う必要があるため、Handlerを利用しています。
> 
> 例外のリネーム
> printStaclTrace というスペルミスを printStackTrace に修正しました。
> 
> これで、Androidアプリ内で簡単にログを出力・表示できるようになります。

テンプレート部分の書き換え
[https://99nyorituryo.hatenablog.com/entry/2024/10/25/000604:embed:cite]


[blog:g:11696248318754550880:banner]