diff --git a/src/main/java/org/alis/excel/model/ImageWrapper.java b/src/main/java/org/alis/excel/model/ImageWrapper.java new file mode 100644 index 0000000..f8d1127 --- /dev/null +++ b/src/main/java/org/alis/excel/model/ImageWrapper.java @@ -0,0 +1,393 @@ +package org.alis.excel.model; + +import com.github.hui.quick.plugin.base.awt.ColorUtil; +import com.github.hui.quick.plugin.base.awt.GraphicUtil; +import com.github.hui.quick.plugin.base.constants.MediaType; +import com.github.hui.quick.plugin.base.file.FileReadUtil; +import com.github.hui.quick.plugin.base.file.FileWriteUtil; +import com.github.hui.quick.plugin.base.gif.GifDecoder; +import com.github.hui.quick.plugin.base.gif.GifHelper; +import com.github.hui.quick.plugin.base.io.IoUtil; +import com.github.hui.quick.plugin.image.helper.ImgPixelHelper; +import com.github.hui.quick.plugin.image.util.FontUtil; +import com.github.hui.quick.plugin.image.wrapper.pixel.ImgPixelOptions; +import com.github.hui.quick.plugin.image.wrapper.pixel.context.PixelContextHolder; +import com.github.hui.quick.plugin.image.wrapper.pixel.model.IPixelStyle; +import com.github.hui.quick.plugin.image.wrapper.pixel.model.PixelStyleEnum; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; + +import static com.github.hui.quick.plugin.image.util.PredicateUtil.conditionGetOrElse; + +/** + * @author lc + * @date 2024/11/9 17:20 + **/ +public class ImageWrapper { + private static final Logger LOG = LoggerFactory.getLogger(ImageWrapper.class); + + private final ImgPixelOptions pixelOptions; + + static { + ImageIO.scanForPlugins(); + } + + public static ImageWrapper.Builder build() { + return new ImageWrapper.Builder(); + } + + private ImageWrapper(ImgPixelOptions pixelOptions) { + this.pixelOptions = pixelOptions; + } + + /** + * 转成像素图片 + * + * @return + */ + public BufferedImage asBufferedImg() { + if (pixelOptions.getSource() == null) { + throw new IllegalArgumentException("bufferedImage cannot be null."); + } + + return parseImg(pixelOptions.getSource()).left; + } + + /** + * 转gif + * + * @return + */ + public ByteArrayOutputStream asGif() { + GifDecoder decoder = pixelOptions.getGifSource(); + if (decoder == null) { + throw new IllegalArgumentException("gif source img cannot be null."); + } + + int gifCnt = decoder.getFrameCount(); + // Pair.left 渲染的字符图, Pair.right gif图展示间隔时间 + List> renderImgList = new ArrayList<>(gifCnt); + for (int index = 0; index < gifCnt; index++) { + ImmutablePair pair = parseImg(decoder.getFrame(index)); + renderImgList.add(ImmutablePair.of(pair.left, decoder.getDelay(index))); + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GifHelper.saveGif(renderImgList, outputStream); + return outputStream; + } + + /** + * 保存到文件 + * + * @param fileName + * @return + * @throws Exception + */ + public boolean asFile(String fileName) throws Exception { + File file = new File(fileName); + FileWriteUtil.mkDir(file.getParentFile()); + + if (pixelOptions.getGifSource() != null) { + try (ByteArrayOutputStream output = asGif()) { + FileOutputStream out = new FileOutputStream(file); + out.write(output.toByteArray()); + out.flush(); + out.close(); + } + return true; + } + + BufferedImage bufferedImage = asBufferedImg(); + if (!ImageIO.write(bufferedImage, pixelOptions.getPicType(), file)) { + throw new IOException("save pixel render image to: " + fileName + " error!"); + } + + return true; + } + + /** + * 图片转字符时,输出字符数组 + * + * @return + */ + public List> asChars() { + // 静态图的转换 + if (pixelOptions.getSource() != null) { + return PixelContextHolder.toPixelChars(parseImg(pixelOptions.getSource()).getRight()); + } + + // gif图的转换 + GifDecoder decoder = pixelOptions.getGifSource(); + int gifCnt = decoder.getFrameCount(); + PixelContextHolder.ImgPixelChar imgPixelChar = null; + for (int index = 0; index < gifCnt; index++) { + ImmutablePair pair = parseImg(decoder.getFrame(index)); + pair.right.setPre(imgPixelChar); + imgPixelChar = pair.right; + } + + return PixelContextHolder.toPixelChars(imgPixelChar); + } + + /** + * 输出svg图 + * + * @return + */ + public List asSvg() { + List> chars = asChars(); + List result = new ArrayList<>(chars.size()); + + for (List txt : chars) { + result.add(ImgPixelHelper.ascii2svg(txt, ColorUtil.color2htmlColor(pixelOptions.getBgColor()), + ColorUtil.color2htmlColor(pixelOptions.getFontColor()))); + } + return result; + } + + /** + * 输出svg文件 + * + * @param file + * @throws Exception + */ + public void asSvgFile(String file) throws Exception { + FileWriteUtil.FileInfo fileInfo = FileWriteUtil.saveFile(file, "svg"); + + List context = asSvg(); + if (context.size() == 1) { + FileWriteUtil.saveContent(fileInfo, context.get(0)); + return; + } + + String lastName = fileInfo.getFilename(); + for (int i = 0; i < context.size(); i++) { + fileInfo.setFilename(lastName + i); + FileWriteUtil.saveFile(fileInfo, context.get(i)); + } + } + + private ImmutablePair parseImg(BufferedImage source) { + if (pixelOptions.getPixelType().scaleUp()) { + return parseAndScaleImg(source); + } + + try { + PixelContextHolder.newPic(); + if (pixelOptions.getRate() != 1) { + source = GraphicUtil.scaleImg((int) (source.getWidth() * pixelOptions.getRate()), (int) (source.getHeight() * pixelOptions.getRate()), source); + } + int picWidth = source.getWidth(); + int picHeight = source.getHeight(); + int blockSize = pixelOptions.getBlockSize(); + IPixelStyle pixelType = pixelOptions.getPixelType(); + BufferedImage pixelImg = new BufferedImage(picWidth / blockSize * blockSize, picHeight / blockSize * blockSize, source.getType()); + Graphics2D g2d = GraphicUtil.getG2d(pixelImg); + g2d.setColor(null); + g2d.fillRect(0, 0, picWidth, picHeight); + for (int y = 0; y < picHeight; y += blockSize) { + for (int x = 0; x < picWidth; x += blockSize) { + pixelType.render(g2d, source, pixelOptions, x, y); + } + } + g2d.dispose(); + return ImmutablePair.of(pixelImg, PixelContextHolder.getPixelChar()); + } finally { + PixelContextHolder.clear(); + } + } + + private ImmutablePair parseAndScaleImg(BufferedImage source) { + try { + PixelContextHolder.newPic(); + int blockSize = pixelOptions.getBlockSize(); + int outWidth = source.getWidth() * blockSize; + int outHeight = source.getHeight() * blockSize; + IPixelStyle pixelType = pixelOptions.getPixelType(); + BufferedImage pixelImg = new BufferedImage(outWidth, outHeight, source.getType()); + Graphics2D g2d = GraphicUtil.getG2d(pixelImg); + g2d.setColor(null); + g2d.fillRect(0, 0, outWidth, outHeight); + for (int y = 0; y < source.getHeight(); y += 1) { + for (int x = 0; x < source.getWidth(); x += 1) { + pixelType.render(g2d, source, pixelOptions, x, y); + } + } + g2d.dispose(); + return ImmutablePair.of(pixelImg, PixelContextHolder.getPixelChar()); + + } finally { + PixelContextHolder.clear(); + } + } + + public static class Builder { + private static final String DEFAULT_CHAR_SET = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\\\"^`'. "; + private static final double DEFAULT_RATE = 1D; + private static final int DEFAULT_BLOCK_SIZE = 1; + private final ImgPixelOptions pixelOptions; + + private String fontName; + private int fontStyle; + + private Builder() { + pixelOptions = new ImgPixelOptions(); + } + + /** + * 设置原始图, 支持gif/jpeg/png + * + * @param img + * @return + */ + public ImageWrapper.Builder setSourceImg(String img) { + try { + return setSourceImg(FileReadUtil.getStreamByFileName(img)); + } catch (IOException e) { + throw new RuntimeException("failed to load " + img, e); + } + } + + /** + * 设置原始图, 支持jpeg/png + * + * @param img + * @return + */ + public ImageWrapper.Builder setSourceImg(BufferedImage img) { + pixelOptions.setSource(img); + return this; + } + + public ImageWrapper.Builder setSourceImg(InputStream inputStream) { + try (ByteArrayInputStream target = IoUtil.toByteArrayInputStream(inputStream)) { + MediaType media = MediaType.typeOfMagicNum(FileReadUtil.getMagicNum(target)); + if (media != null) { + pixelOptions.setPicType(media.getExt()); + } + if (media == MediaType.ImageGif) { + GifDecoder gifDecoder = new GifDecoder(); + gifDecoder.read(target); + pixelOptions.setGifSource(gifDecoder); + } else { + pixelOptions.setSource(ImageIO.read(target)); + } + } catch (IOException e) { + LOG.error("load backgroundImg error!", e); + throw new RuntimeException("load sourceImg error!", e); + } + return this; + } + + public ImageWrapper.Builder setBlockSize(int size) { + pixelOptions.setBlockSize(size); + return this; + } + + /** + * 设置像素模式: 灰度 + 像素化 + 字符化 + * + * @param pixelType + * @return + */ + public ImageWrapper.Builder setPixelType(IPixelStyle pixelType) { + pixelOptions.setPixelType(pixelType); + return this; + } + + /** + * 设置字符图片的字符集 + * + * @param chars + * @return + */ + public ImageWrapper.Builder setChars(String chars) { + pixelOptions.setChars(chars); + return this; + } + + /** + * 设置缩放比例 + * + * @param rate + * @return + */ + public ImageWrapper.Builder setRate(Double rate) { + pixelOptions.setRate(rate); + return this; + } + + + public ImageWrapper.Builder setFontName(String fontName) { + this.fontName = fontName; + return this; + } + + public ImageWrapper.Builder setFontStyle(int fontStyle) { + this.fontStyle = fontStyle; + return this; + } + + public ImageWrapper.Builder setPicType(String picType) { + pixelOptions.setPicType(picType); + return this; + } + + public ImageWrapper.Builder setBgColor(int color) { + pixelOptions.setBgColor(ColorUtil.int2color(color)); + return this; + } + + public ImageWrapper.Builder setBgColor(Color color) { + pixelOptions.setBgColor(color); + return this; + } + + public ImageWrapper.Builder setFontColor(int color) { + pixelOptions.setFontColor(ColorUtil.int2color(color)); + return this; + } + + public ImageWrapper.Builder setFontColor(Color color) { + pixelOptions.setFontColor(color); + return this; + } + + public ImageWrapper.Builder setFontSize(int fontSize) { + pixelOptions.setFontSize(fontSize); + return this; + } + + public ImageWrapper.Builder setBgPredicate(Predicate predicate) { + pixelOptions.setBgPredicate(predicate); + return this; + } + + public ImageWrapper build() { + pixelOptions.setBlockSize(conditionGetOrElse((s) -> s > 0, pixelOptions.getBlockSize(), DEFAULT_BLOCK_SIZE)); + pixelOptions.setPixelType(conditionGetOrElse(Objects::nonNull, pixelOptions.getPixelType(), PixelStyleEnum.CHAR_COLOR)); + pixelOptions.setChars(conditionGetOrElse(Objects::nonNull, pixelOptions.getChars(), DEFAULT_CHAR_SET)); + pixelOptions.setRate(conditionGetOrElse(Objects::nonNull, pixelOptions.getRate(), DEFAULT_RATE)); + pixelOptions.setBgColor(conditionGetOrElse(Objects::nonNull, pixelOptions.getBgColor(), Color.WHITE)); + // 默认将全透明作为背景色 + pixelOptions.setBgPredicate(conditionGetOrElse(Objects::nonNull, pixelOptions.getBgPredicate(), integer -> integer == 0)); + pixelOptions.setFontColor(conditionGetOrElse(Objects::nonNull, pixelOptions.getFontColor(), Color.BLACK)); + pixelOptions.setFontSize(conditionGetOrElse(s -> pixelOptions.getFontSize() > 0, Math.min(pixelOptions.getFontSize(), pixelOptions.getBlockSize()), pixelOptions.getBlockSize())); + fontStyle = conditionGetOrElse((s) -> s >= 0 && s <= 3, fontStyle, Font.ITALIC); + pixelOptions.setFont(FontUtil.getFontOrDefault(fontName, fontStyle, pixelOptions.getFontSize())); + pixelOptions.setPicType(conditionGetOrElse(Objects::nonNull, pixelOptions.getPicType(), MediaType.ImageJpg.getExt())); + return new ImageWrapper(pixelOptions); + } + } + +} diff --git a/src/main/java/org/alis/excel/model/StyleEnum.java b/src/main/java/org/alis/excel/model/StyleEnum.java new file mode 100644 index 0000000..8030a22 --- /dev/null +++ b/src/main/java/org/alis/excel/model/StyleEnum.java @@ -0,0 +1,64 @@ +package org.alis.excel.model; + +import com.github.hui.quick.plugin.base.awt.ColorUtil; +import com.github.hui.quick.plugin.image.wrapper.pixel.ImgPixelOptions; +import com.github.hui.quick.plugin.image.wrapper.pixel.context.PixelContextHolder; +import com.github.hui.quick.plugin.image.wrapper.pixel.model.IPixelStyle; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Objects; + +/** + * @author lc + * @date 2024/11/9 17:02 + **/ +public enum StyleEnum implements IPixelStyle { + + CHAR_SEQ_SCALE_UP { + public Color calculateColor(int red, int green, int blue, int size) { + return null; + } + + @Override + public Color getAverage(int[] colors) { + int red = 0; + int green = 0; + int blue = 0; + for (int color : colors) { + red += ((color & 0xff0000) >> 16); + green += ((color & 0x00ff00) >> 8); + blue += (color & 0x0000ff); + } + int len = colors.length; + return calculateColor(red, green, blue, len); + } + + public boolean scaleUp() { + return true; + } + + public void draw(Graphics2D g2d, ImgPixelOptions options, int x, int y) { + int index = PixelContextHolder.getPixelChar().getAndUpdateSeqIndex(options.getChars().length()); + char ch = options.getChars().charAt(index); + int offsetSize = (options.getBlockSize() - options.getFontSize()) / 2; + g2d.drawString(String.valueOf(ch), x * options.getBlockSize() + offsetSize, y * options.getBlockSize() + offsetSize); + } + + public void render(Graphics2D g2d, BufferedImage source, ImgPixelOptions options, int x, int y) { + int color = source.getRGB(x, y); + if (!options.getBgPredicate().test(color)) { + if (g2d.getFont() == null || !Objects.equals(g2d.getFont().getName(), options.getFont().getName()) || g2d.getFont().getSize() != options.getFontSize()) { + g2d.setFont(options.getFont()); + } + g2d.setColor(ColorUtil.int2color(color)); + this.draw(g2d, options, x, y); + } + } + }; + + StyleEnum() { + + } + +} diff --git a/src/main/java/org/alis/excel/service/ImageService.java b/src/main/java/org/alis/excel/service/ImageService.java index fcd0b94..b845c07 100644 --- a/src/main/java/org/alis/excel/service/ImageService.java +++ b/src/main/java/org/alis/excel/service/ImageService.java @@ -1,11 +1,24 @@ package org.alis.excel.service; +import cn.hutool.http.HttpUtil; +import cn.hutool.http.Method; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; import com.github.hui.quick.plugin.base.awt.ColorUtil; import com.github.hui.quick.plugin.image.wrapper.pixel.ImgPixelWrapper; +import com.github.hui.quick.plugin.image.wrapper.pixel.model.IPixelStyle; import com.github.hui.quick.plugin.image.wrapper.pixel.model.PixelStyleEnum; +import org.alis.excel.model.ImageWrapper; +import org.alis.excel.model.StyleEnum; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; +import sun.misc.BASE64Decoder; +import javax.imageio.ImageIO; import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.Base64; /** * @author lc @@ -16,23 +29,45 @@ public class ImageService implements Runnable { @Override public void run() { try { - ImgPixelWrapper.build() + // 这里采用爽提供的接口服务,抠图获取base64编码的图片 + // 接口地址 https://ai-zjz.cn/api/human_matting post +// String json = HttpUtil.createRequest(Method.POST, "https://ai-zjz.cn/api/human_matting") +// .form("input_image", new File("/Users/lily/Downloads/id.jpeg")) +// .execute().body(); +// JSONObject jsonObj = JSONUtil.parseObj(json); +// String base64 = jsonObj.getStr("image_base64").replace("data:image/png;base64,",""); + // 图片进行解码 +// byte[] decode = Base64.getDecoder().decode(base64); + // 先存图片到本地 +// File opFile = new File("/Users/lily/Downloads/url.jpg"); +// if(!opFile.exists()){ +// if(!opFile.createNewFile()){ +// return; +// } +// } +// BufferedImage readImage = ImageIO.read(new ByteArrayInputStream(decode)); +// ImageIO.write(readImage, "png", new File("/Users/lily/Downloads/url.png")); + ImageWrapper.build() +// .setSourceImg(readImage) .setSourceImg("/Users/lily/Downloads/mylife-removebg-preview.png") .setChars("李萍") .setFontName("Tanugo-S") - .setBlockSize(24) - .setFontSize(22) + .setBlockSize(2) + .setFontSize(1) + .setFontStyle(Font.BOLD) .setBgPredicate(color -> { - // 指定背景色,不渲染文本 +// // 指定背景色,不渲染文本 if (color == 0) return true; Color rc = ColorUtil.int2color(color); // 将白色当作背景色 return rc.getRed() >= 245 && rc.getGreen() >= 245 && rc.getBlue() >= 245; }) - .setPixelType(PixelStyleEnum.CHAR_SEQ_SCALE_UP) +// .setPixelType(PixelStyleEnum.CHAR_SEQ_SCALE_UP) + .setPixelType(StyleEnum.CHAR_SEQ_SCALE_UP) .build().asFile("/Users/lily/Downloads/transfer.png"); } catch (Exception e) { e.printStackTrace(); } } + }