YoRuo:想变得更好

爱折腾 - 乐于折腾 - 享受折腾

欢迎来到我的Blog~


Html转图片的那些事

最近遇到一些关于图片流操作的问题,就总结一下吧。

Html代码转图片并裁剪

找了很多jar都不尽如意。

jar包:html2image

错误的jar地址。http://mvnrepository.com/artifact/com.github.xuwei-k/html2image/0.1.0

正确的jar地址。https://github.com/e-ucm/eadventure/tree/master/etc/repository/gui/ava/html2image

方法调用:

  private static List<BufferedImage> resizeToHeight(BufferedImage image) {
    int width = image.getWidth();
    int height = image.getHeight() / 20;

    List<BufferedImage> images = Lists.newArrayList();

    IntStream.range(0, 20).forEach(i -> {
      BufferedImage ret = image.getSubimage(0, i * height, width, height);
      images.add(ret);
    });
    return images;
  }

  public static void main(String[] args) throws IOException {
    String htmlTxt = "<p style=\"text-align:center;\"><span style=\"font-size:15.0pt;\">0702-13</span></p>\n" +
        "\n" +
        "<p style=\"text-align:center;\"><span style=\"font-size:15.0pt;\">尺码:SML</span></p>\n" +
        "\n" +
        "<p style=\"text-align:center;\"><span style=\"font-family: lisu;\"><span style=\"color: rgb(69, 129, 142);\"><strong><span style=\"font-size: 24px;\">面料;衬衣弹力棉+黑色带弹力西装棉&nbsp;</span></strong></span></span><br />\n" +
        " <span style=\"font-size:15.0pt;\">尺码;SML</span><br />\n" +
        " <span style=\"font-size:15.0pt;\">小码;衬衣 胸围84 衣长59 肩34 袖56 半裙;裤腰66 臀围84 裙长50 马甲随衬衣尺寸</span><br />\n" +
        " <span style=\"font-size:15.0pt;\">中码;衬衣 胸围88 衣长60 肩35 袖57 半裙;裤腰70 臀围88 裙长51 马甲随衬衣尺寸</span><br />\n" +
        " <span style=\"font-size:15.0pt;\">大码;衬衣 胸围88 衣长60 肩36 袖58 半裙;裤腰74 臀围92 裙长52 马甲随衬衣尺寸</span></p>"+
        "<p><img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i1/93733667/TB2V0xIt9FjpuFjSspbXXXagVXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2szRMt88kpuFjSspeXXc7IpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2tZOqt9FjpuFjSszhXXaBuVXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2j4EvtMNlpuFjy0FfXXX3CpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2RAsStHtlpuFjSspfXXXLUpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2uYAZtR0kpuFjy1zdXXXuUVXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" height=\"384.8481308411215\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2rPRMt88kpuFjSspeXXc7IpXa_!!93733667.jpg\" width=\"790\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2iyITtHXlpuFjSszfXXcSGXXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2KOITtHXlpuFjSszfXXcSGXXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i2/93733667/TB27wBetYJkpuFjy1zcXXa5FFXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i1/93733667/TB2dpJFt80kpuFjSsppXXcGTXXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2qQ7FtNXlpuFjSsphXXbJOXXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2m7wStHtlpuFjSspfXXXLUpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2.odstYXlpuFjy1zbXXb_qpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i2/93733667/TB2fGcOtH0kpuFjy0FjXXcBbVXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2RyIltRNkpuFjy0FaXXbRCVXa_!!93733667.jpg\" /></p>";
    HtmlImageGenerator imageGenerator = new HtmlImageGenerator();
    imageGenerator.loadHtml(htmlTxt);
    BufferedImage imageNew = imageGenerator.getBufferedImage();
    
    List<BufferedImage> imageList = resizeToHeight(imageNew);

    for (int i = 0; i < imageList.size(); i++) {
      File outFile = new File("/Users/lxc/Desktop/temp/" + i + ".png");
      ImageIO.write(imageList.get(i), "png", outFile);
    }
  }

第一个方法为裁剪图片,并未对原来的图片进行修改。而是重新创建了一个 BufferedImage 对象

    /**
     * Returns a subimage defined by a specified rectangular region.
     * The returned <code>BufferedImage</code> shares the same
     * data array as the original image.
     * @param x the X coordinate of the upper-left corner of the
     *          specified rectangular region
     * @param y the Y coordinate of the upper-left corner of the
     *          specified rectangular region
     * @param w the width of the specified rectangular region
     * @param h the height of the specified rectangular region
     * @return a <code>BufferedImage</code> that is the subimage of this
     *          <code>BufferedImage</code>.
     * @exception RasterFormatException if the specified
     * area is not contained within this <code>BufferedImage</code>.
     */
    public BufferedImage getSubimage (int x, int y, int w, int h) {
        return new BufferedImage (colorModel,
                                  raster.createWritableChild(x, y, w, h,
                                                             0, 0, null),
                                  colorModel.isAlphaPremultiplied(),
                                  properties);
    }

还有一个通过url拼接图片的方法:

  public static List<File> mergeAndCutPic(List<String> picList) {
    int length = picList.size();
    if (length < 1) {
      return null;
    }
    List<File> files = Lists.newArrayList();
    try {
      BufferedImage[] images = new BufferedImage[length];
      int[][] imageArr = new int[length][];

      for (int i = 0; i < length; i++) {
        images[i] = ImageIO.read(new URL(picList.get(i)).openStream());
        int width = images[i].getWidth();
        int height = images[i].getHeight();
        imageArr[i] = new int[width * height];
        imageArr[i] = images[i].getRGB(0, 0, width, height, imageArr[i], 0, width);
      }

      int tempHeight = 0;
      int tempWidth = images[0].getWidth();

      for (BufferedImage image : images) {
        tempWidth = tempWidth > image.getWidth() ? tempWidth : image.getWidth();
        tempHeight += image.getHeight();
      }

      if (tempHeight < 1) {
        return null;
      }

      BufferedImage imageNew = new BufferedImage(tempWidth, tempHeight,
          BufferedImage.TYPE_INT_RGB);
      int height = 0;
      for (int i = 0; i < images.length; i++) {
        imageNew.setRGB(0, height, tempWidth, images[i].getHeight(), imageArr[i], 0, tempWidth);
        height += images[i].getHeight();
      }

      List<BufferedImage> imageList = resizeToHeight(imageNew);

      for (BufferedImage image : imageList) {
        File outFile = new File("/Users/lxc/Desktop/temp/"
            + System.currentTimeMillis() + UUID.randomUUID() + ".png");
        ImageIO.write(image, "png", outFile);
        files.add(outFile);
      }
    } catch (Exception e) {
      return null;
    }
    return files;
  }

注意要以png为后缀,以jpg结尾会全黑。

至于为啥,我…. 布吉岛。还没看源码,所以也很迷。


Html代码转图片(有中文)

用到一个 cssbox 的 jar。

文档,介绍等 http://cssbox.sourceforge.net/

Github: https://github.com/radkovo/CSSBox

Github的源码版本,pom依赖中的 jstyleparser 2.2-SNAPSHOT maven仓库么找到。只要2.1的。推荐看这个 源码

如果你使用maven或者gradle 配置如下

  compile group: 'net.sf.cssbox', name: 'cssbox', version: '4.12'
  compile group: 'net.sf.cssbox', name: 'jstyleparser', version: '2.1'
  compile group: 'net.sourceforge.nekohtml', name: 'nekohtml', version: '1.9.22'
  compile group: 'xml-apis', name: 'xml-apis', version: '1.4.01'
  compile group: 'xerces', name: 'xercesImpl', version: '2.11.0'

注意 xml-apis 不要换成新的版本了。

不然将会看到这个异常

ClassNotFoundException: org.w3c.dom.ElementTraversal

新版本已经淘汰这个接口。

转换方法调用我做了一些修改。

  public static List<File> renderURL(String url) throws Exception {
    if (!url.startsWith("http:") &&
        !url.startsWith("https:") &&
        !url.startsWith("ftp:") &&
        !url.startsWith("file:"))
      url = "http://" + url;

    DocumentSource docSource = new DefaultDocumentSource(url);
    DOMSource parser = new DefaultDOMSource(docSource);
    parser.setContentType("text/html; charset=utf-8");
    Document doc = parser.parse();

    MediaSpec media = new MediaSpec("screen");
    media.setDimensions(windowSize.width, windowSize.height);
    media.setDeviceDimensions(windowSize.width, windowSize.height);

    DOMAnalyzer da = new DOMAnalyzer(doc, docSource.getURL());
    da.setMediaSpec(media);
    da.attributesToStyles();
    da.addStyleSheet(null, CSSNorm.stdStyleSheet(), DOMAnalyzer.Origin.AGENT);
    da.addStyleSheet(null, CSSNorm.userStyleSheet(), DOMAnalyzer.Origin.AGENT);
    da.addStyleSheet(null, CSSNorm.formsStyleSheet(), DOMAnalyzer.Origin.AGENT);
    da.getStyleSheets();

    BrowserCanvas contentCanvas = new BrowserCanvas(da.getRoot(), da, docSource.getURL());
    contentCanvas.setAutoMediaUpdate(false);
    contentCanvas.getConfig().setClipViewport(false);
    contentCanvas.getConfig().setLoadImages(true);
    contentCanvas.getConfig().setLoadBackgroundImages(true);
    contentCanvas.createLayout(windowSize);

    docSource.close();
    return resizeToHeight(contentCanvas.getImage());
  }

如果么有这句话 parser.setContentType("text/html; charset=utf-8"); 中文将会乱码。

如果在服务器上发现会乱码而本地测试不会。可能是因为服务器没有中文的字体

https://juejin.im/entry/576e0c79816dfa0055d2f017


Html代码转图片后的压缩

因为淘宝的详情总图片大小最多 2.5m

所以需要进行压缩。

图片压缩使用了一个jar

compile 'net.coobird:thumbnailator:0.4.8'

可以根据比例,高度等进行压缩,质量还是可以的。

具体用法自行看源码吧 上一个Util的代码。

package mbxc.util;

import com.google.common.collect.Lists;
import cz.vutbr.web.css.MediaSpec;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.fit.cssbox.css.CSSNorm;
import org.fit.cssbox.css.DOMAnalyzer;
import org.fit.cssbox.io.DOMSource;
import org.fit.cssbox.io.DefaultDOMSource;
import org.fit.cssbox.io.DefaultDocumentSource;
import org.fit.cssbox.io.DocumentSource;
import org.fit.cssbox.layout.BrowserCanvas;
import org.w3c.dom.Document;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.UUID;
import java.util.stream.IntStream;

/**
 * Created by LXC on 2017/5/10.
 */
@Slf4j
public class ImageUtils {

  public static final Integer WIDTH = 750;

  private static final Dimension windowSize = new Dimension(750, 600);

  /**
   * 读取图片
   *
   * @param imgUrl
   * @throws IOException
   */
  public static BufferedImage createImage(String imgUrl) throws IOException {
    return ImageIO.read(new URL(imgUrl).openStream());
  }

  /**
   * 以宽度为基准,等比例放缩图片
   *
   * @param w int 新宽度
   */
  public static File resizeByWidth(BufferedImage img, int w) throws IOException {
    int width = img.getWidth();    // 得到源图宽
    int height = img.getHeight();  // 得到源图长
    int h = (height * w / width);
    return resize(img, w, h);
  }

  /**
   * 以高度为基准,等比例缩放图片
   *
   * @param h int 新高度
   */
  public static File resizeByHeight(BufferedImage img, int h) throws IOException {
    int width = img.getWidth();    // 得到源图宽
    int height = img.getHeight();  // 得到源图长
    int w = (width * h / height);
    return resize(img, w, h);
  }

  /**
   * 强制压缩/放大图片到固定的大小
   *
   * @param w int 新宽度
   * @param h int 新高度
   */
  private static File resize(BufferedImage img, int w, int h) throws IOException {
    String filePath = "/data/static/image/newFile.jpg";
    File file = new File(filePath);
    if (!file.exists()) {
      file.createNewFile();
    }
    Thumbnails.of(img).size(w, h).toFile(file);
    return file;
  }

  public static List<File> generateImage(String htmlPath) {
    try {
      return renderURL(htmlPath);
    } catch (Exception e) {
      return null;
    }
  }

  public static List<File> mergeAndCutPic(List<String> picList) {
    int length = picList.size();
    if (length < 1) {
      return null;
    }
    List<File> files = Lists.newArrayList();
    try {
      BufferedImage[] images = new BufferedImage[length];
      int[][] imageArr = new int[length][];

      for (int i = 0; i < length; i++) {
        images[i] = ImageIO.read(new URL(picList.get(i)).openStream());
        int width = images[i].getWidth();
        int height = images[i].getHeight();
        imageArr[i] = new int[width * height];
        imageArr[i] = images[i].getRGB(0, 0, width, height, imageArr[i], 0, width);
      }

      int tempHeight = 0;
      int tempWidth = images[0].getWidth();

      for (BufferedImage image : images) {
        tempWidth = tempWidth > image.getWidth() ? tempWidth : image.getWidth();
        tempHeight += image.getHeight();
      }

      if (tempHeight < 1) {
        return null;
      }

      BufferedImage imageNew = new BufferedImage(tempWidth, tempHeight,
          BufferedImage.TYPE_INT_RGB);
      int height = 0;
      for (int i = 0; i < images.length; i++) {
        imageNew.setRGB(0, height, tempWidth, images[i].getHeight(), imageArr[i], 0, tempWidth);
        height += images[i].getHeight();
      }
      return resizeToHeight(imageNew);
    } catch (Exception e) {
      return null;
    }
  }

  private static List<File> resizeToHeight(BufferedImage image) throws IOException {
    int width = image.getWidth();
    int height = image.getHeight() / 20;

    List<BufferedImage> images = Lists.newArrayList();

    IntStream.range(0, 20).forEach(i -> images.add(image.getSubimage(0, i * height, width, height)));
    List<File> files = Lists.newArrayList();

    for (BufferedImage img : images) {
      File outFile = new File("/Users/lxc/Desktop/temp/"
          + System.currentTimeMillis() + UUID.randomUUID() + ".jpg");
      ImageIO.write(img, "jpg", outFile);
      Thumbnails.of(outFile).scale(0.5).toFile(outFile);
      files.add(outFile);
    }
    return files;
  }

  public static void main(String[] args) {
    generateImage("file:/Users/lxc/Downloads/test.html");
  }

  private static List<File> renderURL(String url) throws Exception {
    if (!url.startsWith("http:") &&
        !url.startsWith("https:") &&
        !url.startsWith("ftp:") &&
        !url.startsWith("file:"))
      url = "http://" + url;

    DocumentSource docSource = new DefaultDocumentSource(url);
    DOMSource parser = new DefaultDOMSource(docSource);
    parser.setContentType("text/html; charset=utf-8");
    Document doc = parser.parse();

    MediaSpec media = new MediaSpec("screen");
    media.setDimensions(windowSize.width, windowSize.height);
    media.setDeviceDimensions(windowSize.width, windowSize.height);

    DOMAnalyzer da = new DOMAnalyzer(doc, docSource.getURL());
    da.setMediaSpec(media);
    da.attributesToStyles();
    da.addStyleSheet(null, CSSNorm.stdStyleSheet(), DOMAnalyzer.Origin.AGENT);
    da.addStyleSheet(null, CSSNorm.userStyleSheet(), DOMAnalyzer.Origin.AGENT);
    da.addStyleSheet(null, CSSNorm.formsStyleSheet(), DOMAnalyzer.Origin.AGENT);
    da.getStyleSheets();

    BrowserCanvas contentCanvas = new BrowserCanvas(da.getRoot(), da, docSource.getURL());
    contentCanvas.setAutoMediaUpdate(false);
    contentCanvas.getConfig().setClipViewport(false);
    contentCanvas.getConfig().setLoadImages(true);
    contentCanvas.getConfig().setLoadBackgroundImages(true);
    contentCanvas.createLayout(windowSize);

    docSource.close();
    return resizeToHeight(contentCanvas.getImage());
  }
}