@ -26,14 +26,19 @@ import java.awt.Dimension;
import java.awt.Graphics2D ;
import java.awt.Image ;
import java.awt.MediaTracker ;
import java.awt.Rectangle ;
import java.awt.image.BufferedImage ;
import java.awt.image.Raster ;
import java.io.ByteArrayInputStream ;
import java.io.File ;
import java.io.ByteArrayOutputStream ;
import java.io.IOException ;
import java.net.MalformedURLException ;
import java.io.InputStream ;
import java.util.Iterator ;
import java.util.Map ;
import javax.imageio.ImageIO ;
import javax.imageio.ImageReader ;
import javax.imageio.stream.ImageInputStream ;
import net.yacy.cora.document.id.DigestURL ;
import net.yacy.cora.document.id.MultiProtocolURL ;
import net.yacy.cora.federate.yacy.CacheStrategy ;
@ -44,12 +49,11 @@ import net.yacy.cora.protocol.RequestHeader;
import net.yacy.cora.storage.ConcurrentARC ;
import net.yacy.cora.util.ConcurrentLog ;
import net.yacy.data.URLLicense ;
import net.yacy.document.ImageParser ;
import net.yacy.kelondro.util.FileUtils ;
import net.yacy.kelondro.util.MemoryControl ;
import net.yacy.kelondro.workflow.WorkflowProcessor ;
import net.yacy.peers.graphics.EncodedImage ;
import net.yacy.repository.Blacklist.BlacklistType ;
import net.yacy.repository.LoaderDispatcher ;
import net.yacy.search.Switchboard ;
import net.yacy.server.serverObjects ;
import net.yacy.server.serverSwitch ;
@ -58,17 +62,30 @@ public class ViewImage {
private static Map < String , Image > iconcache = new ConcurrentARC < String , Image > ( 1000 ,
Math . max ( 10 , Math . min ( 32 , WorkflowProcessor . availableCPU * 2 ) ) ) ;
private static String defaulticon = "htroot/env/grafics/dfltfvcn.ico" ;
private static byte [ ] defaulticonb ;
static {
try {
defaulticonb = FileUtils . read ( new File ( defaulticon ) ) ;
} catch ( final IOException e ) {
}
}
public static Object respond ( final RequestHeader header , final serverObjects post , final serverSwitch env ) {
/ * *
* Try parsing image from post "url" parameter or from "code" parameter .
* When image format is not supported , return directly image data . When
* image could be parsed , try encoding to target format specified by header
* "EXT" .
*
* @param header
* request header
* @param post
* post parameters
* @param env
* environment
* @return an { @link EncodedImage } instance encoded in format specified in
* post , or an InputStream pointing to original image data
* @throws IOException
* when specified url is malformed , or a read / write error
* occured , or input or target image format is not supported .
* Sould end in a HTTP 500 error whose processing is more
* consistent across browsers than a response with zero content
* bytes .
* /
public static Object respond ( final RequestHeader header , final serverObjects post , final serverSwitch env )
throws IOException {
final Switchboard sb = ( Switchboard ) env ;
@ -85,25 +102,13 @@ public class ViewImage {
| | sb . verifyAuthentication ( header ) ; // handle access rights
DigestURL url = null ;
if ( ( urlString . length ( ) > 0 ) & & ( auth ) )
try {
url = new DigestURL ( urlString ) ;
} catch ( final MalformedURLException e1 ) {
url = null ;
}
if ( ( urlString . length ( ) > 0 ) & & ( auth ) ) {
url = new DigestURL ( urlString ) ;
}
if ( ( url = = null ) & & ( urlLicense . length ( ) > 0 ) ) {
urlString = URLLicense . releaseLicense ( urlLicense ) ;
try {
url = new DigestURL ( urlString ) ;
} catch ( final MalformedURLException e1 ) {
url = null ;
urlString = null ;
}
}
if ( urlString = = null ) {
return null ;
url = new DigestURL ( urlString ) ;
}
// get the image as stream
@ -115,52 +120,81 @@ public class ViewImage {
if ( image ! = null ) {
encodedImage = new EncodedImage ( image , ext , post . getBoolean ( "isStatic" ) ) ;
} else {
byte [ ] resourceb = null ;
if ( url ! = null )
try {
String agentName = post . get ( "agentName" , auth ? ClientIdentification . yacyIntranetCrawlerAgentName
: ClientIdentification . yacyInternetCrawlerAgentName ) ;
ClientIdentification . Agent agent = ClientIdentification . getAgent ( agentName ) ;
resourceb = sb . loader . loadContent ( sb . loader . request ( url , false , true ) , CacheStrategy . IFEXIST ,
BlacklistType . SEARCH , agent ) ;
} catch ( final IOException e ) {
ConcurrentLog . fine ( "ViewImage" , "cannot load: " + e . getMessage ( ) ) ;
}
boolean okToCache = true ;
if ( resourceb = = null ) {
if ( urlString . endsWith ( ".ico" ) ) {
// load default favicon dfltfvcn.ico
// Should not do this here : we can be displaying search
// image result of '.ico' type and do not want to display a
// default
if ( defaulticonb = = null )
try {
resourceb = FileUtils . read ( new File ( sb . getAppPath ( ) , defaulticon ) ) ;
okToCache = false ;
} catch ( final IOException e ) {
return null ;
}
else {
resourceb = defaulticonb ;
okToCache = false ;
}
} else {
return null ;
}
}
String urlExt = MultiProtocolURL . getFileExtension ( url . getFileName ( ) ) ;
if ( ext ! = null & & ext . equalsIgnoreCase ( urlExt ) & & isBrowserRendered ( urlExt ) ) {
return new ByteArrayInputStream ( resourceb ) ;
return openInputStream ( post , sb . loader , auth , url ) ;
}
// read image
encodedImage = parseAndScale ( post , auth , urlString , ext , okToCache , resourceb ) ;
ImageInputStream imageInStream = null ;
InputStream inStream = null ;
/ *
* When opening a file , the most efficient is to open
* ImageInputStream directly on file
* /
if ( url . isFile ( ) ) {
imageInStream = ImageIO . createImageInputStream ( url . getFSFile ( ) ) ;
} else {
inStream = openInputStream ( post , sb . loader , auth , url ) ;
imageInStream = ImageIO . createImageInputStream ( inStream ) ;
}
try {
// read image
encodedImage = parseAndScale ( post , auth , urlString , ext , imageInStream ) ;
} finally {
/ *
* imageInStream . close ( ) method doesn ' t close source input
* stream
* /
if ( inStream ! = null ) {
try {
inStream . close ( ) ;
} catch ( IOException ignored ) {
}
}
}
}
return encodedImage ;
}
/ * *
* Open input stream on image url using provided loader . All parameters must
* not be null .
*
* @param post
* post parameters .
* @param loader .
* Resources loader .
* @param auth
* true when user has credentials to load full images .
* @param url
* image url .
* @return an open input stream instance ( don ' t forget to close it ) .
* @throws IOException
* when a read / write error occured .
* /
private static InputStream openInputStream ( final serverObjects post , final LoaderDispatcher loader ,
final boolean auth , DigestURL url ) throws IOException {
InputStream inStream = null ;
if ( url ! = null ) {
try {
String agentName = post . get ( "agentName" , auth ? ClientIdentification . yacyIntranetCrawlerAgentName
: ClientIdentification . yacyInternetCrawlerAgentName ) ;
ClientIdentification . Agent agent = ClientIdentification . getAgent ( agentName ) ;
inStream = loader . openInputStream ( loader . request ( url , false , true ) , CacheStrategy . IFEXIST ,
BlacklistType . SEARCH , agent ) ;
} catch ( final IOException e ) {
ConcurrentLog . fine ( "ViewImage" , "cannot load: " + e . getMessage ( ) ) ;
throw e ;
}
}
if ( inStream = = null ) {
throw new IOException ( "Input stream could no be open" ) ;
}
return inStream ;
}
/ * *
* @param formatName
* informal file format name . For example : "png" .
@ -180,73 +214,177 @@ public class ViewImage {
}
/ * *
* Process resourceb byte array to try to produce an Image instance
* eventually scaled and cropped depending on post parameters
* Process source image to try to produce an EncodedImage instance
* eventually scaled and clipped depending on post parameters . When
* processed , imageInStream is closed .
*
* @param post
* request post parameters . Must not be null .
* @param auth
* true when access rigths are OK .
* @param urlString
* image source URL . Must not be null .
* image source URL as String . Must not be null .
* @param ext
* image file extension . May be null .
* @param okToCache
* true when image can be cached
* @ param resourceb
* byte array . Must not be null .
* @return an Image instance when parsing is OK , or null .
* target image file format . May be null .
* @param imageInStream
* open stream on image content . Must not be null .
* @ return an EncodedImage instance .
* @throws IOException
* when image could not be parsed or encoded to specified format
* /
protected static EncodedImage parseAndScale ( serverObjects post , boolean auth , String urlString , String ext ,
boolean okToCache , byte [ ] resourceb ) {
ImageInputStream imageInStream ) throws IOException {
EncodedImage encodedImage = null ;
Image image = ImageParser . parse ( urlString , resourceb ) ;
if ( image ! = null ) {
int maxwidth = post . getInt ( "maxwidth" , 0 ) ;
int maxheight = post . getInt ( "maxheight" , 0 ) ;
final boolean quadratic = post . containsKey ( "quadratic" ) ;
boolean isStatic = post . getBoolean ( "isStatic" ) ;
if ( ! auth | | maxwidth ! = 0 | | maxheight ! = 0 ) {
// find original size
int h = image . getHeight ( null ) ;
int w = image . getWidth ( null ) ;
// in case of not-authorized access shrink the image to
// prevent
// copyright problems, so that images are not larger than
// thumbnails
Dimension maxDimensions = calculateMaxDimensions ( auth , w , h , maxwidth , maxheight ) ;
// if a quadratic flag is set, we cut the image out to be in
// quadratic shape
if ( quadratic & & w ! = h ) {
image = makeSquare ( image , h , w ) ;
h = image . getHeight ( null ) ;
w = image . getWidth ( null ) ;
}
Dimension finalDimensions = calculateDimensions ( w , h , maxDimensions ) ;
// BufferedImage image = ImageIO.read(imageInStream);
Iterator < ImageReader > readers = ImageIO . getImageReaders ( imageInStream ) ;
if ( ! readers . hasNext ( ) ) {
try {
/* When no reader can be found, we have to close the stream */
imageInStream . close ( ) ;
} catch ( IOException ignoredException ) {
}
/ *
* Throw an exception , wich will end in a HTTP 500 response , better
* handled by browsers than an empty image
* /
throw new IOException ( "Image format is not supported." ) ;
}
ImageReader reader = readers . next ( ) ;
reader . setInput ( imageInStream , true , true ) ;
int maxwidth = post . getInt ( "maxwidth" , 0 ) ;
int maxheight = post . getInt ( "maxheight" , 0 ) ;
final boolean quadratic = post . containsKey ( "quadratic" ) ;
boolean isStatic = post . getBoolean ( "isStatic" ) ;
BufferedImage image = null ;
boolean returnRaw = true ;
if ( ! auth | | maxwidth ! = 0 | | maxheight ! = 0 ) {
// find original size
final int originWidth = reader . getWidth ( 0 ) ;
final int originHeigth = reader . getHeight ( 0 ) ;
// in case of not-authorized access shrink the image to
// prevent
// copyright problems, so that images are not larger than
// thumbnails
Dimension maxDimensions = calculateMaxDimensions ( auth , originWidth , originHeigth , maxwidth , maxheight ) ;
// if a quadratic flag is set, we cut the image out to be in
// quadratic shape
int w = originWidth ;
int h = originHeigth ;
if ( quadratic & & originWidth ! = originHeigth ) {
Rectangle square = getMaxSquare ( originHeigth , originWidth ) ;
h = square . height ;
w = square . width ;
}
if ( w ! = finalDimensions . width & & h ! = finalDimensions . height ) {
image = scale ( finalDimensions . width , finalDimensions . height , image ) ;
Dimension finalDimensions = calculateDimensions ( w , h , maxDimensions ) ;
if ( originWidth ! = finalDimensions . width | | originHeigth ! = finalDimensions . height ) {
returnRaw = false ;
image = readImage ( reader ) ;
if ( quadratic & & originWidth ! = originHeigth ) {
image = makeSquare ( image ) ;
}
if ( ( finalDimensions . width = = 16 ) & & ( finalDimensions . height = = 16 ) & & okToCache ) {
// this might be a favicon, store image to cache for
// faster
// re-load later on
iconcache . put ( urlString , image ) ;
image = scale ( finalDimensions . width , finalDimensions . height , image ) ;
}
if ( finalDimensions . width = = 16 & & finalDimensions . height = = 16 ) {
// this might be a favicon, store image to cache for
// faster
// re-load later on
if ( image = = null ) {
returnRaw = false ;
image = readImage ( reader ) ;
}
iconcache . put ( urlString , image ) ;
}
}
/* Image do not need to be scaled or cropped */
if ( returnRaw ) {
if ( ! reader . getFormatName ( ) . equalsIgnoreCase ( ext ) | | imageInStream . getFlushedPosition ( ) ! = 0 ) {
/ *
* image parsing and reencoding is only needed when source image
* and target formats differ , or when first bytes have been discarded
* /
returnRaw = false ;
image = readImage ( reader ) ;
}
}
if ( returnRaw ) {
byte [ ] imageData = readRawImage ( imageInStream ) ;
encodedImage = new EncodedImage ( imageData , ext , isStatic ) ;
} else {
/ *
* An error can still occur when transcoding from buffered image to
* target ext : in that case return null
* /
encodedImage = new EncodedImage ( image , ext , isStatic ) ;
if ( encodedImage . getImage ( ) . length ( ) = = 0 ) {
throw new IOException ( "Image could not be encoded to format : " + ext ) ;
}
}
return encodedImage ;
}
/ * *
* Read image using specified reader and close ImageInputStream source .
* Input must have bean set before using
* { @link ImageReader # setInput ( Object ) }
*
* @param reader
* image reader . Must not be null .
* @return buffered image
* @throws IOException
* when an error occured
* /
private static BufferedImage readImage ( ImageReader reader ) throws IOException {
BufferedImage image ;
try {
image = reader . read ( 0 ) ;
} finally {
reader . dispose ( ) ;
Object input = reader . getInput ( ) ;
if ( input instanceof ImageInputStream ) {
try {
( ( ImageInputStream ) input ) . close ( ) ;
} catch ( IOException ignoredException ) {
}
}
}
return image ;
}
/ * *
* Read image data without parsing .
*
* @param inStream
* image source . Must not be null . First bytes must not have been marked discarded ( { @link ImageInputStream # getFlushedPosition ( ) } must be zero )
* @return image data as bytes
* @throws IOException
* when a read / write error occured .
* /
private static byte [ ] readRawImage ( ImageInputStream inStream ) throws IOException {
byte [ ] buffer = new byte [ 4096 ] ;
int l = 0 ;
ByteArrayOutputStream outStream = new ByteArrayOutputStream ( ) ;
inStream . seek ( 0 ) ;
try {
while ( ( l = inStream . read ( buffer ) ) > = 0 ) {
outStream . write ( buffer , 0 , l ) ;
}
return outStream . toByteArray ( ) ;
} finally {
try {
inStream . close ( ) ;
} catch ( IOException ignored ) {
}
}
}
/ * *
* Calculate image dimensions from image original dimensions , max
* dimensions , and target dimensions .
@ -317,9 +455,9 @@ public class ViewImage {
* image to scale . Must not be null .
* @return a scaled image
* /
protected static Image scale ( final int width , final int height , Image image ) {
protected static Buffered Image scale ( final int width , final int height , final Buffered Image image ) {
// compute scaled image
final Image scaled = image . getScaledInstance ( width , height , Image . SCALE_AREA_AVERAGING ) ;
Image scaled = image . getScaledInstance ( width , height , Image . SCALE_AREA_AVERAGING ) ;
final MediaTracker mediaTracker = new MediaTracker ( new Container ( ) ) ;
mediaTracker . addImage ( scaled , 0 ) ;
try {
@ -328,45 +466,91 @@ public class ViewImage {
}
// make a BufferedImage out of that
final BufferedImage i = new BufferedImage ( width , height , BufferedImage . TYPE_INT_ RGB) ;
BufferedImage result = new BufferedImage ( width , height , BufferedImage . TYPE_INT_ A RGB) ;
try {
i . createGraphics ( ) . drawImage ( scaled , 0 , 0 , width , height , null ) ;
image = i ;
result . createGraphics ( ) . drawImage ( scaled , 0 , 0 , width , height , null ) ;
// check outcome
final Raster raster = i . getData ( ) ;
int [ ] pixel = new int [ 3 ] ;
final Raster raster = result . getData ( ) ;
int [ ] pixel = new int [ raster . getSampleModel ( ) . getNumBands ( ) ] ;
pixel = raster . getPixel ( 0 , 0 , pixel ) ;
if ( pixel [ 0 ] ! = 0 | | pixel [ 1 ] ! = 0 | | pixel [ 2 ] ! = 0 )
image = i ;
} catch ( final Exception e ) {
// java.lang.ClassCastException: [I cannot be cast to [B
/ *
* Exception may be caused by source image color model : try now to
* convert to RGB before scaling
* /
try {
BufferedImage converted = EncodedImage . convertToRGB ( image ) ;
scaled = converted . getScaledInstance ( width , height , Image . SCALE_AREA_AVERAGING ) ;
mediaTracker . addImage ( scaled , 1 ) ;
try {
mediaTracker . waitForID ( 1 ) ;
} catch ( final InterruptedException e2 ) {
}
result = new BufferedImage ( width , height , BufferedImage . TYPE_INT_ARGB ) ;
result . createGraphics ( ) . drawImage ( scaled , 0 , 0 , width , height , null ) ;
// check outcome
final Raster raster = result . getData ( ) ;
int [ ] pixel = new int [ result . getSampleModel ( ) . getNumBands ( ) ] ;
pixel = raster . getPixel ( 0 , 0 , pixel ) ;
} catch ( Exception e2 ) {
result = image ;
}
ConcurrentLog . fine ( "ViewImage" , "Image could not be scaled" ) ;
}
return image ;
return result ;
}
/ * *
*
* @param h
* image height
* @param w
* image width
* @return max square area fitting inside dimensions
* /
protected static Rectangle getMaxSquare ( final int h , final int w ) {
Rectangle square ;
if ( w > h ) {
final int offset = ( w - h ) / 2 ;
square = new Rectangle ( offset , 0 , h , h ) ;
} else {
final int offset = ( h - w ) / 2 ;
square = new Rectangle ( 0 , offset , w , w ) ;
}
return square ;
}
/ * *
* Crop image to make a square
*
* @param image
* image to crop
* @param h
* @param w
* @return
* /
protected static Image makeSquare ( Image image , final int h , final int w ) {
protected static BufferedImage makeSquare ( BufferedImage image ) {
final int w = image . getWidth ( ) ;
final int h = image . getHeight ( ) ;
if ( w > h ) {
final BufferedImage dst = new BufferedImage ( h , h , BufferedImage . TYPE_INT_RGB ) ;
final BufferedImage dst = new BufferedImage ( h , h , BufferedImage . TYPE_INT_ A RGB) ;
Graphics2D g = dst . createGraphics ( ) ;
final int offset = ( w - h ) / 2 ;
g . drawImage ( image , 0 , 0 , h - 1 , h - 1 , offset , 0 , h + offset , h - 1 , null ) ;
g . dispose ( ) ;
try {
g . drawImage ( image , 0 , 0 , h - 1 , h - 1 , offset , 0 , h + offset , h - 1 , null ) ;
} finally {
g . dispose ( ) ;
}
image = dst ;
} else {
final BufferedImage dst = new BufferedImage ( w , w , BufferedImage . TYPE_INT_RGB ) ;
final BufferedImage dst = new BufferedImage ( w , w , BufferedImage . TYPE_INT_ A RGB) ;
Graphics2D g = dst . createGraphics ( ) ;
final int offset = ( h - w ) / 2 ;
g . drawImage ( image , 0 , 0 , w - 1 , w - 1 , 0 , offset , w - 1 , w + offset , null ) ;
g . dispose ( ) ;
try {
g . drawImage ( image , 0 , 0 , w - 1 , w - 1 , 0 , offset , w - 1 , w + offset , null ) ;
} finally {
g . dispose ( ) ;
}
image = dst ;
}
return image ;