/*
* This file is part of the Haven & Hearth game client.
* Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and
* Björn Johannessen <johannessen.bjorn@gmail.com>
*
* Redistribution and/or modification of this file is subject to the
* terms of the GNU Lesser General Public License, version 3, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Other parts of this source tree adhere to other copying
* rights. Please see the file `COPYING' in the root directory of the
* source tree for details.
*
* A copy the GNU Lesser General Public License is distributed along
* with the source tree of which this file is a part in the file
* `doc/LPGL-3'. If it is missing for any reason, please see the Free
* Software Foundation's website at <http://www.fsf.org/>, or write
* to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package haven;
import java.util.*;
import java.awt.Color;
import java.awt.image.*;
import java.awt.color.ColorSpace;
import javax.imageio.*;
import javax.imageio.metadata.*;
import javax.imageio.stream.*;
import org.w3c.dom.*;
import java.io.*;
import java.net.*;
public class Screenshooter extends Window {
public static final ComponentColorModel outcm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8}, false, false, ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
public final URL tgt;
public final Shot shot;
private final TextEntry comment;
private final CheckBox decobox, pub;
private final int w, h;
private Label prog;
private Label progSave;
private Coord btnc, btns;
private Button btn, btnSave;
public Screenshooter(Coord c, Widget parent, URL tgt, Shot shot) {
super(c, Coord.z, parent, "Screenshot");
this.tgt = tgt;
this.shot = shot;
this.w = Math.min(200 * shot.sz.x / shot.sz.y, 150);
this.h = w * shot.sz.y / shot.sz.x;
this.decobox = new CheckBox(new Coord(w, (h - CheckBox.box.sz().y) / 2), this, "Include interface");
this.decobox.a = Config.ss_ui;
Label clbl = new Label(new Coord(0, h + 5), this, "If you wish, leave a comment:");
this.comment = new TextEntry(new Coord(0, clbl.c.y + clbl.sz.y + 5), w + 130, this, "") {
public void activate(String text) {
upload();
}
};
this.pub = new CheckBox(new Coord(0, comment.c.y + comment.sz.y + 5), this, "Make public");
pub.a = true;
btnc = new Coord((comment.sz.x - 125) / 2, pub.c.y + pub.sz.y + 20);
btns = btnc.add(130, 0);
btn = new Button(btnc, 125, this, "Upload") {
public void click() {
upload();
}
};
btnSave = new Button(btns, 125, this, "Save"){
public void click(){
save();
}
};
pack();
}
public void wdgmsg(Widget sender, String msg, Object... args) {
if((sender == this) && (msg == "close")) {
ui.destroy(this);
} else {
super.wdgmsg(sender, msg, args);
}
}
public void cdraw(GOut g) {
TexI tex = this.decobox.a?shot.ui:shot.map;
g.image(tex, Coord.z, new Coord(w, h));
}
public static class Shot {
public final TexI map, ui;
public final Coord sz;
public String comment;
public boolean fsaa, fl, sdw;
public Shot(TexI map, TexI ui) {
this.map = map;
this.ui = ui;
this.sz = map.sz();
}
}
public static interface ImageFormat {
public String ctype();
public void write(OutputStream out, BufferedImage img, Shot info) throws IOException;
}
public static final ImageFormat png = new ImageFormat() {
public String ctype() {return("image/png");}
void cmt(Node tlist, String key, String val) {
Element cmt = new IIOMetadataNode("TextEntry");
cmt.setAttribute("keyword", key);
cmt.setAttribute("value", val);
cmt.setAttribute("encoding", "utf-8");
cmt.setAttribute("language", "");
cmt.setAttribute("compression", "none");
tlist.appendChild(cmt);
}
public void write(OutputStream out, BufferedImage img, Shot info) throws IOException {
ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(img);
ImageWriter wr = ImageIO.getImageWriters(type, "PNG").next();
IIOMetadata dat = wr.getDefaultImageMetadata(type, null);
Node root = dat.getAsTree("javax_imageio_1.0");
Node tlist = new IIOMetadataNode("Text");
if(info.comment != null)
cmt(tlist, "Comment", info.comment);
cmt(tlist, "haven.fsaa", info.fsaa?"y":"n");
cmt(tlist, "haven.flight", info.fl?"y":"n");
cmt(tlist, "haven.sdw", info.sdw?"y":"n");
cmt(tlist, "haven.conf", Config.confid);
root.appendChild(tlist);
dat.setFromTree("javax_imageio_1.0", root);
ImageOutputStream iout = ImageIO.createImageOutputStream(out);
wr.setOutput(iout);
wr.write(new IIOImage(img, null, dat));
}
};
public static final ImageFormat jpeg = new ImageFormat() {
public String ctype() {return("image/jpeg");}
public void write(OutputStream out, BufferedImage img, Shot info) throws IOException {
ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(img);
ImageWriter wr = ImageIO.getImageWriters(type, "JPEG").next();
IIOMetadata dat = wr.getDefaultImageMetadata(type, null);
Node root = dat.getAsTree("javax_imageio_jpeg_image_1.0");
Node mseq;
for(mseq = root.getFirstChild(); (mseq != null) && !mseq.getLocalName().equals("markerSequence"); mseq = mseq.getNextSibling());
if(mseq == null) {
mseq = new IIOMetadataNode("markerSequence");
root.appendChild(mseq);
}
if(info.comment != null) {
IIOMetadataNode cmt = new IIOMetadataNode("com");
cmt.setUserObject(info.comment.getBytes("utf-8"));
mseq.appendChild(cmt);
}
Message hdat = new Message(0);
hdat.addstring2("HSSI1");
hdat.addstring("fsaa");
hdat.addstring(info.fsaa?"y":"n");
hdat.addstring("flight");
hdat.addstring(info.fl?"y":"n");
hdat.addstring("sdw");
hdat.addstring(info.sdw?"y":"n");
hdat.addstring("conf");
hdat.addstring(Config.confid);
IIOMetadataNode app4 = new IIOMetadataNode("unknown");
app4.setAttribute("MarkerTag", "228");
app4.setUserObject(hdat.blob);
mseq.appendChild(app4);
dat.setFromTree("javax_imageio_jpeg_image_1.0", root);
ImageOutputStream iout = ImageIO.createImageOutputStream(out);
wr.setOutput(iout);
wr.write(new IIOImage(img, null, dat));
}
};
public class Uploader extends HackThread {
private final TexI img;
private final Shot info;
private final ImageFormat fmt;
public Uploader(TexI img, Shot info, ImageFormat fmt) {
super("Screenshot uploader");
this.img = img;
this.info = info;
this.fmt = fmt;
}
public void run() {
try {
upload(img, info, fmt);
} catch(InterruptedIOException e) {
setstate("Cancelled");
synchronized(ui) {
ui.destroy(btn);
btn = new Button(btnc, 125, Screenshooter.this, "Retry") {
public void click() {
Screenshooter.this.upload();
}
};
}
} catch(IOException e) {
setstate("Could not upload image");
synchronized(ui) {
ui.destroy(btn);
btn = new Button(btnc, 125, Screenshooter.this, "Retry") {
public void click() {
Screenshooter.this.upload();
}
};
}
}
}
private void setstate(String t) {
synchronized(ui) {
if(prog != null)
ui.destroy(prog);
prog = new Label(btnc.sub(0, 15), Screenshooter.this, t);
}
}
private BufferedImage convert(BufferedImage img) {
WritableRaster buf = PUtils.byteraster(PUtils.imgsz(img), 3);
BufferedImage ret = new BufferedImage(outcm, buf, false, null);
java.awt.Graphics g = ret.getGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
return(ret);
}
public void upload(TexI ss, Shot info, ImageFormat fmt) throws IOException {
setstate("Preparing image...");
ByteArrayOutputStream buf = new ByteArrayOutputStream();
fmt.write(buf, convert(ss.back), info);
byte[] data = buf.toByteArray();
buf = null;
setstate("Connecting...");
URL pared = Utils.urlparam(tgt, "p", pub.a?"y":"n");
HttpURLConnection conn = (HttpURLConnection)pared.openConnection();
conn.setDoOutput(true);
conn.setFixedLengthStreamingMode(data.length);
conn.addRequestProperty("Content-Type", fmt.ctype());
Message auth = new Message(0);
auth.addstring2(ui.sess.username + "/");
auth.addbytes(ui.sess.sesskey);
conn.addRequestProperty("Authorization", "Haven " + Utils.base64enc(auth.blob));
conn.connect();
OutputStream out = conn.getOutputStream();
try {
int off = 0;
while(off < data.length) {
setstate(String.format("Uploading (%d%%)...", (off * 100) / data.length));
int len = Math.min(1024, data.length - off);
out.write(data, off, len);
off += len;
}
} finally {
out.close();
}
setstate("Awaiting response...");
InputStream in = conn.getInputStream();
final URL result;
try {
if(!conn.getContentType().equals("text/x-target-url"))
throw(new IOException("Unexpected type of reply from server"));
byte[] b = Utils.readall(in);
try {
result = new URL(new String(b, "utf-8"));
} catch(MalformedURLException e) {
throw((IOException)new IOException("Unexpected reply from server").initCause(e));
}
} finally {
in.close();
}
setstate("Done");
synchronized(ui) {
ui.destroy(btn);
btn = new Button(btnc, 125, Screenshooter.this, "Open in browser") {
public void click() {
if(WebBrowser.self != null)
WebBrowser.self.show(result);
}
};
}
}
}
public void upload() {
shot.comment = comment.text;
final Uploader th = new Uploader(decobox.a?shot.ui:shot.map, shot, Config.ss_compress?jpeg:png);
th.start();
ui.destroy(btn);
btn = new Button(btnc, 125, this, "Cancel") {
public void click() {
th.interrupt();
}
};
}
public class Saver extends HackThread {
private final TexI img;
private final Shot info;
private final ImageFormat fmt;
public Saver(TexI img, Shot info, ImageFormat fmt) {
super("Screenshot saver");
this.img = img;
this.info = info;
this.fmt = fmt;
}
public void run() {
try {
save(img, info, fmt);
} catch(IOException e) {
setstate("Could not save image");
synchronized(ui) {
ui.destroy(btn);
btn = new Button(btns, 125, Screenshooter.this, "Retry") {
public void click() {
Screenshooter.this.save();
}
};
}
}
}
private void setstate(String t) {
synchronized(ui) {
if(progSave != null)
ui.destroy(progSave);
progSave = new Label(btns.sub(0, 15), Screenshooter.this, t);
}
}
private BufferedImage convert(BufferedImage img) {
WritableRaster buf = PUtils.byteraster(PUtils.imgsz(img), 3);
BufferedImage ret = new BufferedImage(outcm, buf, false, null);
java.awt.Graphics g = ret.getGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
return(ret);
}
public void save(TexI ss, Shot info, ImageFormat fmt) throws IOException {
setstate("Preparing image...");
ByteArrayOutputStream buf = new ByteArrayOutputStream();
fmt.write(buf, convert(ss.back), info);
byte[] data = buf.toByteArray();
buf = null;
File ssfolder = Config.getFile("screenshots");
if(!ssfolder.exists()){
ssfolder.mkdirs();
}
String fname = String.format("shot_%s_%d.%s",
Utils.current_date(),
System.currentTimeMillis(),
Config.ss_compress?"jpeg":"png");
File f = new File(ssfolder, fname );
FileOutputStream fos = new FileOutputStream(f);
fos.write(data);
fos.close();
setstate("Done");
final URL result = f.toURI().toURL();
synchronized(ui) {
ui.destroy(btnSave);
btnSave = new Button(btns, 125, Screenshooter.this, "Open") {
public void click() {
if(WebBrowser.self != null)
WebBrowser.self.show(result);
}
};
ui.message("Screenshot saved", GameUI.MsgType.INFO);
}
}
}
protected void save() {
final Saver th = new Saver(decobox.a?shot.ui:shot.map, shot, Config.ss_compress?jpeg:png);
th.start();
ui.destroy(btnSave);
}
public static void take(final GameUI gameui, final URL tgt) {
if(gameui == null){return;}
new Object() {
TexI[] ss = {null, null};
{
gameui.map.delay2(new MapView.Delayed() {
public void run(GOut g) {
ss[0] = new TexI(g.getimage(Coord.z, g.sz));
ss[0].minfilter = javax.media.opengl.GL.GL_LINEAR;
checkcomplete(g);
}
});
gameui.ui.drawaftertt(new UI.AfterDraw() {
public void draw(GOut g) {
ss[1] = new TexI(g.getimage(Coord.z, g.sz));
checkcomplete(g);
}
});
}
private void checkcomplete(GOut g) {
if((ss[0] != null) && (ss[1] != null)) {
Shot shot = new Shot(ss[0], ss[1]);
shot.fl = g.gc.pref.flight.val;
shot.sdw = g.gc.pref.lshadow.val;
shot.fsaa = g.gc.pref.fsaa.val;
Screenshooter s = new Screenshooter(new Coord(100, 100), gameui, tgt, shot);
if(Config.ss_silent){
s.visible = false;
s.save();
s.wdgmsg(s, "close", (Object[])null);
}
}
}
};
}
}