Pregunta

Tengo una aplicación de código abierto que sube fotos a Facebook. Para ahorrar ancho de banda, las fotos se redimensionan automáticamente antes de subirlas (Facebook impone un límite de tamaño máximo). Algunas personas se han quejado de la calidad de la foto y, de hecho, puede ver la diferencia (consulte este problema para algunas imágenes de demostración).

Entonces mi pregunta es, ¿cuál es el "mejor"? ¿Cómo reducir las imágenes (es decir, fotos) en Java sin perder calidad, o al menos, con una pérdida / artefactos de calidad mínima?

Puede ver el código actual que tengo aquí (cambiar el tamaño del código a través de esta página ).

¿Fue útil?

Solución

Lo he intentado todo, incluidos los trucos aquí , y todo lo que puedo decir es que es mejor usar ImageMagick con cualquier interfaz, las bibliotecas de imágenes Javas simplemente no están a la altura cuando se trata de esto. Debe admitir tantos formatos y algoritmos para hacerlo bien.

Otros consejos

Phil, no sé con qué solución finalmente fuiste, pero escalar imágenes en Java puede verse bastante bien si:

  • Evite los tipos de BufferedImage que no sean compatibles con el JDK.
  • Usar escala incremental
  • Apéguese a bicubic cuando use escala incremental

He hecho una buena cantidad de pruebas con estos métodos y la escala incremental junto con la adherencia a tipos de imágenes bien soportados es la clave: veo que Alexander mencionó que todavía no tuvo buena suerte con eso, lo cual es un fastidio .

Lancé la biblioteca imgscalr (Apache 2) Hace unos 6 meses para abordar el problema de "Quiero copias a escala atractivas de esta imagen, ¡HAZLO AHORA!" después de leer algo así como 10 preguntas como esta en SO.

El uso estándar se ve así:

BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, 640);

El segundo argumento es el ancho y la altura delimitadores que imgscalr usará para escalar la imagen, manteniendo sus proporciones correctas incluso si pasó en dimensiones no válidas, hay muchos métodos más detallados , pero ese es el uso más simple.

El caso de uso que desearía, por ejemplo, si Facebook limitara las imágenes a 800x600 píxeles, se vería así:

BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, Method.QUALITY, 800, 600);

Eso asegurará que la imagen permanezca en el mejor tipo de imagen compatible y escalada con el método de más alta calidad que Java pueda reunir.

En mis propias pruebas de alta resolución, no he notado ninguna discrepancia enorme con las imágenes escaladas usando esta biblioteca / estos métodos EXCEPTO cuando el cargador de ImageIO coloca su imagen en un tipo de imagen poco compatible; por ejemplo, esto sucede mucho con GIFs. Si los dejas así y no los sacas de esos tipos mal soportados, terminará pareciendo realmente aturdido y terrible.

La razón de esto es que el equipo de Java2D en realidad tiene diferentes tuberías aceleradas por hardware para todos los diferentes tipos de BufferedImages que el JDK puede procesar: un subconjunto de esos tipos de imágenes que son menos comunes recurren al uso de la misma representación de software canalización debajo de las cubiertas en Java2D, lo que resulta en imágenes de aspecto pobre y a veces totalmente incorrecto. Este fue un PIA para explicar e intentar descubrir que acabo de escribir esa lógica directamente en la biblioteca.

Los dos tipos más compatibles son BufferedImage.TYPE_INT_RGB y _ARGB si tiene curiosidad.

Las dos bibliotecas de código abierto más populares que se especializan en cambiar el tamaño de la imagen en Java actualmente son:

Además, existe la forma JDK con < code> Graphics2D ( vea esta pregunta sobre cómo hacerlo ), que es conocida por crear malos resultados, especialmente con reducción de escala . También hay una interfaz Java para ImageMagick que se omitirá aquí porque requiere una herramienta externa.

Calidad visual

Aquí hay una comparación de los resultados del cambio de tamaño / reducción de escala de un 580x852 png a 145x213 . Como referencia Photoshop CS5 " guardar para web " Se utiliza el cambio de tamaño. Nota: los resultados son 1: 1 lo que las bibliotecas creadas se copiaron juntas. El zoom no utiliza ningún filtro, solo un algoritmo vecino más cercano. Aquí puede encuentra la imagen original.

 comparación

  
      
  1. Thumbnailator 0.4.8 con la configuración predeterminada, sin ajustes de dimensión
  2.   
  3. Photoshop CS5 con algoritmo bicúbico
  4.   
  5. imgscalr 4.2 con configuración ULTRA_QUALITY, sin ajustes de dimensión
  6.   
  7. Graphics2D (Java 8) con sugerencias de renderizado VALUE_INTERPOLATION_BICUBIC, VALUE_RENDER_QUALITY, VALUE_ANTIALIAS_ON
  8.   

Dejo que el lector seleccione el mejor resultado, ya que es subjetivo. En general, todos tienen una buena salida, excepto Graphics2D . Thumbnailator genera imágenes más nítidas muy similares a la salida de Photoshop, mientras que la salida de imgscalr es considerablemente más suave. Para los iconos / texto, etc., desea una salida más nítida, para las imágenes puede que desee una salida más suave.

Tiempo computacional

Aquí hay un punto de referencia no científico que utiliza esta herramienta y 114 imágenes con una dimensión de aproximadamente < code> 96x96 hasta 2560x1440 tratándolo como 425% de imágenes creando: 100%, 150%, 200%, 300% y 400% de versiones escaladas (por lo que 114 * 5 operaciones de escala ) Todas las bibliotecas usan la misma configuración que en la comparación de calidad (por lo tanto, la mejor calidad posible). Los tiempos solo están escalando, no todo el proceso. Hecho en un i5-2520M con 8 GB de RAM y 5 carreras.

  
      
  • Thumbnailator : 7003.0ms | 6581.3ms | 6019,1 ms | 6375.3ms | 8700.3ms
  •   
  • imgscalr : 25218.5ms | 25786.6ms | 25095.7ms | 25790,4 ms | 29296.3ms
  •   
  • Graphics2D : 7387.6ms | 7177.0ms | 7048.2ms | 7132,3 ms | 7510.3ms
  •   

Aquí está el código utilizado en este punto de referencia.

Curiosamente Thumbnailator también es el más rápido con un tiempo promedio de 6.9 segundos seguido de Java2D con 7.2 segundos dejando a imgscalr atrás con un pobre 26.2 segundos >. Esto probablemente no sea justo ya que imgscalr está configurado en ULTRA_QUALITY , lo que parece ser extremadamente costoso; con la configuración QUALITY tiene un promedio de 11.1 segundos más competitivo

Para cambiar el tamaño de la imagen con calidad personalizada, use thumbnailator.jar.

Código de ejemplo    http://code.google.com/p/thumbnailator/wiki/Examples

¿Qué sugerencia de representación estás usando? Por lo general, el remuestreo bicúbico será el mejor. En las fotos a las que se vincula, son muy irregulares, lo que me hace pensar que está utilizando al vecino más cercano como su pista.

En la PictureScaler que vincula para, en el método paintComponent , utiliza seis medios diferentes para cambiar el tamaño de la imagen. ¿Has probado los seis para ver cuál da el mejor resultado?

Después de algunos experimentos frustrantes, encontré lo siguiente redimensionar evaluación , y empleé el enfoque de múltiples pasos en mi proyecto.

Para hacer eso, copié el método getScaledInstance () en mi clase de generador de miniaturas, cambié mi enfoque de lectura de imagen para usar ImageIO (que devuelve una BufferedImage) y ahora estoy muy contento

Comparé el resultado con un cambio de tamaño realizado en Photoshop CS3 y el resultado es muy similar.

Quería cambiar el tamaño de la más alta calidad con la relación de aspecto preservada. Intenté algunas cosas y leí varias entradas. Perdí dos días y al final obtuve el mejor resultado con el método Java simple (probé también las bibliotecas ImageMagick y java-image-scaling):

public static boolean resizeUsingJavaAlgo(String source, File dest, int width, int height) throws IOException {
  BufferedImage sourceImage = ImageIO.read(new FileInputStream(source));
  double ratio = (double) sourceImage.getWidth()/sourceImage.getHeight();
  if (width < 1) {
    width = (int) (height * ratio + 0.4);
  } else if (height < 1) {
    height = (int) (width /ratio + 0.4);
  }

  Image scaled = sourceImage.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
  BufferedImage bufferedScaled = new BufferedImage(scaled.getWidth(null), scaled.getHeight(null), BufferedImage.TYPE_INT_RGB);
  Graphics2D g2d = bufferedScaled.createGraphics();
  g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
  g2d.drawImage(scaled, 0, 0, width, height, null);
  dest.createNewFile();
  writeJpeg(bufferedScaled, dest.getCanonicalPath(), 1.0f);
  return true;
}


/**
* Write a JPEG file setting the compression quality.
*
* @param image a BufferedImage to be saved
* @param destFile destination file (absolute or relative path)
* @param quality a float between 0 and 1, where 1 means uncompressed.
* @throws IOException in case of problems writing the file
*/
private static void writeJpeg(BufferedImage image, String destFile, float quality)
      throws IOException {
  ImageWriter writer = null;
  FileImageOutputStream output = null;
  try {
    writer = ImageIO.getImageWritersByFormatName("jpeg").next();
    ImageWriteParam param = writer.getDefaultWriteParam();
    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    param.setCompressionQuality(quality);
    output = new FileImageOutputStream(new File(destFile));
    writer.setOutput(output);
    IIOImage iioImage = new IIOImage(image, null, null);
    writer.write(null, iioImage, param);
  } catch (IOException ex) {
    throw ex;
  } finally {
    if (writer != null) {
      writer.dispose();
    }
    if (output != null) {
      output.close();
    }
  }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top