2008年5月6日火曜日

面白いPNGの利用法

nihilogic: Compression using Canvas and PNG-embedded data 面白い人もいるもんだな~。 JavaScriptファイルをPNGファイルにしちゃって、CanvasのgetImageDataを使って再度JavaScriptに復元。 画像ファイルはあまりにも砂嵐。スクリプトの文字コードをそのままRGBにセットして、画像を生成してるからね。なので、8ビット(0~255)グレースケールの画像になるし、1文字が1ピクセル。そりゃ砂嵐だぜ。 CanvasはIEじゃ使えないからFirefoxとOpera、あと最新のWebkitでしか、この方法は使えませんよ、って言う事なんですね。 スゴイな。こんな発想なかった。GZipでいいじゃね~か、と思う自分がちょっと恥ずかしいっす。 スクリプトから画像生成するのはPHPのコードになってたけど、そのままだと試しにくいので、C#で書きなおしてみた(そのままデス)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace MakeJSPng.Controllers
{
 public class HomeController : Controller
 {
   public void Index()
   {
     string js = this.ReadFromRequest("JSSource") + "";
     int hash = Math.Abs(js.GetHashCode());
     string fileName = string.Format("{0}.png", hash);
     string filePath = Request.MapPath("~/PNGs/") + fileName;

     if (js.Length > 0)
     {
       ViewData["JSSource"] = js;
       byte[] bytes = Encoding.UTF8.GetBytes(js);
       int width = (int)Math.Ceiling(Math.Sqrt((double)bytes.Length));
       int height = width;

       using (Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb))
       {
         int pos = 0;
         for (int y = 0; y < height && pos < bytes.Length; y++)
         {
           for (int x = 0; x < width && pos < bytes.Length; x++)
           {
             int val = (int)bytes[pos++];
             bmp.SetPixel(x, y, Color.FromArgb(0, val, val, val));
           }
         }
         bmp.Save(filePath, ImageFormat.Png);
       }
     }

     ViewData["PngPath"] = "/PNGs/" + fileName;

     RenderView("Index");
   }

   public void About()
   {
     RenderView("About");
   }
 }
} 

ASP.NET MVC Preview2で動くよ。 Home/Index.aspxは↓。

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MakeJSPng.Views.Home.Index" %>
<%@ Import Namespace="MakeJSPng.Controllers" %>
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContentPlaceHolder" runat="server">
<script type="text/javascript" src="/Content/pngdata.js"></script>
<% using (Html.Form<HomeController>(c=>c.Index())) { %>

<p>スクリプト</p>
<%=Html.TextArea("JSSource", ViewData["JSSource"]) %>

<p>
<%=Html.SubmitButton("Make", "生成") %>
</p>

<p>画像</p>
<img src="<%= ViewData["PngPath"] %>" id="png" />

<p>復元</p>
<%=Html.TextArea("PNGSource", "") %>
<% } %>


<script type="text/javascript">

var path = '<%= ViewData["PngPath"] %>';
loadPNGData(path, function decodeCallback(data){
 var element = document.getElementById("PNGSource");
 element.value = data;
});

</script>
</asp:Content>

でも、IEじゃ動かないよ! 試しに、prototype.1.6.0.2.jsを圧縮した画像↓。 img.aspx

砂嵐。 本家はこれが30KBなんだけど、こっちは50KBなんでじゃ~!!画像サイズ少し違うし。PHPでのデフォルトエンコードがUTF8じゃないのかもしれないしね。 本家は8bppだけど、こっちは24bppなのが原因?それじゃしょうがないよね! ところでPHPのコードは良くわかんない(GDが良くわかんない?)けど、TrueColorを保存して自動で8ビットになるんでしょうか? ちょっとした息抜きってことで。