管理部門で奮闘中の元ソフトウェアエンジニアによる日々雑感

DelphiのTBitmap用 画像縮小コード

前に作ってたソフトで使っていたソースを公開。
DHGL(Delphi High-Level Graphic Library)のソースを元に加工しています。
Delphiだけでは最適化が難しいので、一部C++でSSE命令で記述しDLL化したものをcall。
SSE使用法の参考にもなるかと。

Delphi

unit BitmapUtils;

interface

uses Windows, Classes, Graphics, SysUtils;


function Shrink_SSE(Bitmap: TBitmap; Width, Height: Integer): TBitmap;

type
    TTriple = packed record
        B, G, R: Byte;
    end;
    TTripleArray = array[0..40000000] of TTriple;
    PTripleArray = ^TTripleArray;

procedure Shrink_2(S, N:pointer; SW, SH, DW, DH:integer); stdcall; external 'exf.dll';

implementation

function Shrink_SSE(Bitmap: TBitmap; Width, Height: Integer): TBitmap;
type
    TDoubleTriple = record
        B, G, R: double;
    end;
var
    NewBitmap, SourceBitmap: TBitmap;

    // 変換元のビットマップの大きさ
    SourceWidth, SourceHeight: Integer;

    // スキャンラインポインタのキャッシュ
    SourceScans, NewScans: array of PTripleArray;
    
    i: Integer;

    ScanLinePtr0  : Pointer;
    ScanLineOffset: Longint;
begin
    SourceWidth := Bitmap.Width; SourceHeight := Bitmap.Height;
    
    NewBitmap := TBitmap.Create;
    try
        // 変換先 ビットマップを作る
        NewBitmap.PixelFormat := pf24bit;
        NewBitmap.Width := Width; NewBitmap.Height := Height;
        
        // 変換元をフルカラーにする
        SourceBitmap := TBitmap.Create;
        try
            SourceBitmap.Assign(Bitmap);
            SourceBitmap.PixelFormat := pf24Bit;
            
            // スキャンラインポインタのキャッシュを作る
            SetLength(SourceScans, SourceHeight);
            SetLength(NewScans, Height);
            
            ScanLinePtr0:=SourceBitmap.ScanLine[0];
            if(SourceBitmap.Height > 0) then
                ScanLineOffset := Integer(SourceBitmap.ScanLine[1])-Integer(SourceBitmap.ScanLine[0]);
            
            for i := 0 to SourceHeight-1 do
                SourceScans[i] := Pointer(Integer(ScanLinePtr0)+ScanLineOffset*i);
            
            ScanLinePtr0:=NewBitmap.ScanLine[0];
            if(NewBitmap.Height > 0) then
                ScanLineOffset := Integer(NewBitmap.ScanLine[1])-Integer(NewBitmap.ScanLine[0]);
            
            for i := 0 to Height-1 do
                NewScans[i] := Pointer(Integer(ScanLinePtr0)+ScanLineOffset*i);
            
            Shrink_2(SourceScans, NewScans, SourceWidth, SourceHeight, Width, Height);
        finally
            SourceBitmap.Free;
        end;
    except
        NewBitmap.Free;
        Raise;
    end;
    
    Result := NewBitmap;
end;

end.

C++

#include <xmmintrin.h>

#pragma pack(1)
typedef struct {
    unsigned char B;
    unsigned char G;
    unsigned char R;
} TTriple;
#pragma pack()

typedef __declspec(align(16)) struct {
    float R;
    float G;
    float B;
    float Dummy;
} TFloatTriple;

inline int      trunc_double_sse2(double x)
{
    __asm {
        cvttsd2si eax, x
    }
}

inline int      trunc_float_sse(float x)
{
    __asm {
        cvttss2si eax, x
    }
}

extern "C" void __stdcall Shrink_2(TTriple **sourceScans, TTriple **newScans, int sourceWidth, int sourceHeight, int width, int height);

void __stdcall Shrink_2(TTriple **sourceScans, TTriple **newScans, int sourceWidth, int sourceHeight, int width, int height)
{
    int x, y, i, j;
    TTriple *pSourceScan;
    TTriple *pNewScan;
    double rectTop, rectLeft, rectRight, rectBottom;
    int trunced_rectTop, trunced_rectBottom;
    int rectLeft_i, rectRight_i;
    float ratio;
    double xRatio, yRatio;
    TFloatTriple pixel;
    TTriple sourcePixel;
    float w, h;
    
    __m128 xmmv_ratio_rcp;
    __m128 xmmv_w;
    __m128 xmmv_spx;
    __m128 xmmv_dpx;
    
    
    ratio = (double)(sourceWidth * sourceHeight) / (double)width / (double)height;
    xRatio = (double)sourceWidth / (double)width;
    yRatio = (double)sourceHeight / (double)height;
    
    xmmv_ratio_rcp = _mm_set_ps1(1 / ratio);
    
    for(y=0; y<height; y++) {
        pNewScan = newScans[y];
        
        rectTop    = y * yRatio;
        rectBottom = (y+1) * yRatio - 0.000001;
        
        trunced_rectTop = trunc_double_sse2(rectTop);
        trunced_rectBottom = trunc_double_sse2(rectBottom);
        
        for(x=0; x<width; x++) {
            // 変換先ピクセルを変換元に投影する。
            rectLeft   = x * xRatio;
            rectRight  = (x+1) * xRatio - 0.000001;
            
            rectLeft_i = trunc_double_sse2(rectLeft);
            rectRight_i = trunc_double_sse2(rectRight);

            // 変換元に投影された変換先ピクセルと交わっている
            // 変換元ピクセルを選び出し積分する
            //pixel.R = 0; pixel.G = 0; pixel.B = 0;
            xmmv_dpx = _mm_setzero_ps();

            for(j=trunced_rectTop; j<=trunced_rectBottom; j++) {
                pSourceScan = sourceScans[j];
                
                _mm_prefetch((char *)pSourceScan, _MM_HINT_NTA);
                
                for(i=rectLeft_i; i<=rectRight_i; i++) {
                    sourcePixel = pSourceScan[i];
                    
                    //_mm_prefetch((char *)&sourcePixel, _MM_HINT_NTA);

                    // 投影されたピクセルと変換元ピクセルの交わっている
                    // 部分の大きさを求める
                    if( (rectLeft < i) && ((i+1) < rectRight) )
                        w = 1;
                    else if( (i <= rectLeft) && ((i+1) < rectRight) )
                        w = 1 - (rectLeft - i);
                    else if( (rectLeft < i) && (rectRight <= (i+1)) )
                        w = rectRight - i;
                    else
                        w = rectRight - rectLeft;

                    if( (rectTop < j) && ((j+1) < rectBottom) )
                        h = 1;
                    else if( (j <= rectTop) && ((j+1) < rectBottom) )
                        h = 1 - (rectTop - j);
                    else if( (rectTop < j) && (rectBottom < (j+1)) )
                        h = rectBottom - j;
                    else
                        h = rectBottom - rectTop;
                    
                    xmmv_w = _mm_set_ps1(w * h);
                    xmmv_spx = _mm_setr_ps(sourcePixel.R, sourcePixel.G, sourcePixel.B, 0);
                    
                    xmmv_w = _mm_mul_ps(xmmv_w, xmmv_spx);
                    xmmv_dpx = _mm_add_ps(xmmv_dpx, xmmv_w);
                    
                    // 変換元 1 ピクセル分 積分
                    /*
                    pixel.R = pixel.R + w * h * sourcePixel.R;
                    pixel.G = pixel.G + w * h * sourcePixel.G;
                    pixel.B = pixel.B + w * h * sourcePixel.B;
                    */
                }
            }
            
            xmmv_dpx = _mm_mul_ps(xmmv_dpx, xmmv_ratio_rcp);
            _mm_store_ps((float *)&pixel, xmmv_dpx);
            
            
            // 積分値から平均値を求め変換先に代入する
            /*
            pNewScan[x].R = round(pixel.R / ratio);
            pNewScan[x].G = round(pixel.G / ratio);
            pNewScan[x].B = round(pixel.B / ratio);
            */
            
            pNewScan[x].R = trunc_float_sse(pixel.R);
            pNewScan[x].G = trunc_float_sse(pixel.G);
            pNewScan[x].B = trunc_float_sse(pixel.B);
        }
    }
}

だいぶ放置・・・

だいぶ放置してました。
RaspberryPi用のSDカードが壊れて依頼、本体は引き出しに眠ったままです。
面白い活用方法はないか考え中なんですがイマイチ思いつかない・・・
皆さんどんな風に使われてるんでしょう?

まあでも、これは好奇心で色々弄れる健全な大人のおもちゃですね。
思いついたときにまた電源入れることにしよう!

【Raspberry Pi】 webサーバー準備中

Raspberry Piにwebサーバーを一応構築したものの、
前に使ってた自作blogを少し変更中のため公開遅れてます。
エディタ部分にはCKEditorを使おうか考えてますが、
iPhoneから使いやすくするために、はてな記法のような独自の記述方法を考えて実装してみようかとも考え中。

【Raspberry Pi】 VNCで接続

sshで接続して操作で十分なんですが、せっかくなのでVNCで接続できるよう設定します。

軽いと言われているTightVNCをインストール。

$ sudo apt-get install tightvncserver

SDカードが遅いせいか多少時間がかかりましたがインストール完了。
ディスプレイ番号0、解像度1024x768、24bitカラーにて起動。

$ vncserver :0 -geometry 1024x768 -depth 24

初回はパスワードを設定するよう聞かれるので入力。
なぜか8文字までしか認識しなく、長い場合は切り捨てられるようなので注意。
次にViewモードのパスワードを設定するか聞かれますがNを選択。
(表示のみって何に使うんでしょうか・・?)
珍しくログを取ったので貼っておきます。

You will require a password to access your desktops.

Password:
Warning: password truncated to the length of 8.
Verify:
Would you like to enter a view-only password (y/n)? n
xauth:  file /home/pi/.Xauthority does not exist

New 'X' desktop is raspberrypi:0

Creating default startup script /home/pi/.vnc/xstartup
Starting applications specified in /home/pi/.vnc/xstartup
Log file is /home/pi/.vnc/raspberrypi:0.log

WindowsからVNCクライアントを使って接続。今回は有名なRealVNCを使ってみます。
サーバに「RaspberryPiのIP:5900(+ディスプレイ番号)」で接続。
RaspberryPiのIPが192.168.1.20でディスプレイ番号が0なら「192.168.1.20:5900」を指定。
f:id:zeder:20130223230105p:plain

VNCサーバーを終了するには -killオプションにディスプレイ番号を指定する。

$ vncserver -kill :0

【Raspberry Pi】 Nginx・PHPインストール

webサーバーといえばApacheが有名ですが、
軽い・速い・メモリ消費量が少ない・使いやすい「Nginx」をインストールします。
下記に簡潔にに手順を記載。

$ sudo apt-get install nginx

ついでにPHPもインストールしておきます。(MySQLモジュールも一緒に)

$ sudo apt-get install php5 php5-fpm php5-cgi php5-cli php5-common php5-mysql

piユーザーディレクトリに www/logs と www/html を作成。

$ cd   ※piユーザーホームへ移動
$ mkdir -p www/{logs,html}


[Nginxの設定]
/home/pi/www/ 配下に対する設定

$ sudo vi /etc/nginx/sites-available/pi
server {
     listen 80;
     server_name localhost;

     access_log /home/pi/www/logs/access.log;
     error_log /home/pi/www/logs/error.log;

     location / {
          root /home/pi/www/html;
          index index.html index.php;
     }

     location ~ \.php$ {
          #fastcgi_pass 127.0.0.1:9000;
          fastcgi_pass unix:/var/run/php5-fpm.sock;
          fastcgi_index index.php;
          include fastcgi_params;
          fastcgi_param SCRIPT_FILENAME /home/pi/www/html$fastcgi_script_name;
     }

     location ~ /\.ht {
          deny all;
     }
}

サイトを有効にするため、上記ファイルを /etc/nginx/sites-enabled/ へシンボリックリンクを張る。

$ sudo ln -s /etc/nginx/sites-available/pi /etc/nginx/sites-enabled/

Nginxの設定ファイルに問題が無いかチェック。

$ sudo nginx -t

問題がなければ設定再読み込み。

$ sudo nginx -s reload

PHPが動作するかphpinfo()で検証。
/home/pi/www/html/配下に下記のPHPコードでindex.phpを作成。

<?php phpinfo(); ?>

ブラウザでRaspberryPiのIPアドレスにアクセスし、
Nginx, PHPが動作していることを確認する。