1. [BlogEngine.NET] 改造工程 - 整合 FunP 推推王

    古早以前,曾替我的 BLOG 加上推推王的小貼紙,不過當時也僅止於把 CODE 加上去而以,成效不大好...。這次搬家搬到 BlogEngine 後,又開始一樣的循環了..,要不要加上這些共用書籤? 要加那一套? 目前台灣用的最多就是黑米推推王了。

    原本挑了黑米,只因為它有提供 [黑米卡],正好取代掉 BlogEngine 右邊那塊 [關於作者] .. 不過試用的情況不怎麼理想,除了速度有點慢之外,同一頁放太多 (幾十個) 的速度也很慢,也許跟 BlogEngine 我選用的樣板有點不合,速度太慢時有時整個版面就毀了,下載到一半就掛掉...

    相對之下,看了看 FunP 提供的 SCRIPT,看起來 CODING STYLE 比較合我的胃口,速度也快一些,沒碰到會讓我版面掛掉的問題。另外使用上的流程 FunP 也簡單一點,本來想兩家的書籤都放的,到最後就決定支持一下交大的學弟,就全力跟 FunP 推推王整合好了。

    動手前先計劃了一下,毫無目的的把一堆 CODE 加上去,我最忌誨這樣弄了,看起來一點主題都沒有。常看到別人的 BLOG 滿滿一堆標籤,從國內的 FunP,黑米,MyShare,到國外的DELICIUS,還有一堆叫不出名字的,一字排開落落長...

    BlogEngine 原本也有內建一些,不過被我拿掉了。底下列出我調整的前後差異:

    1. 捨棄內建的 Rating 機制,直接用推文就好。
    2. 版面我希望類似原有 CS 的樣式,正好拿推文按鈕來取代原本的計數器
    3. 就鎖定一個共用書簽就好,把原有左下方的其它都拿掉
    4. Tags 我也決定捨棄不用,以分類為主。因此左下的 Tags 就移掉了
    5. 分類放右下很礙眼,移到右上
    6. 不爽被盜文,加上一段智財權的聲明
    7. 加上自己補的計數器... (說來話長,請見下一篇)
    8. 推文時自動帶出文章的基本資訊,如標題,內文,標簽等等

     

    原 CS 的樣式:

    image

     

     

    修改前:

    image

    修改後:

    image

     

    看了一下推推王的工具,不外乎都是插入一段 <SCRIPT> 標簽,然後用 document.write( ) 或是 eval 等等 client side script 的方式產生片 HTML Code, 缺點就是繞了一大圈,出了問題也常讓人搞不清楚問題在那裡。花了點時間追一下,追出最後插在網頁的 HTML CODE 長這樣:

    [copy code]
        <IFRAME	width=60 	height=55	marginWidth=0 	marginHeight=0 	frameBorder=0 	scrolling=no 	src="http://funp.com/tools/buttoniframe.php?url=xxxxxxxxxxxxxx&s=1" mce_src="http://funp.com/tools/buttoniframe.php?url=xxxxxxxxxxxxxx&s=1">    </IFRAME>
    
       1:    <IFRAME
    
       2:  width=60 
    
       3:  height=55
    
       4:  marginWidth=0 
    
       5:  marginHeight=0 
    
       6:  frameBorder=0 
    
       7:  scrolling=no 
    
       8:  src="http://funp.com/tools/buttoniframe.php?url=xxxxxxxxxxxxxx&s=1">
    
       9:    </IFRAME>
    

     

     

     

    看起來就是直接產生一個帶著指定參數的 <IFRAME ...>,於是我在 BlogEngine Themes 版面就直接產生 <IFRAME> ...底下是 BlogEngine THEME 目錄下的 PostView.ascx 片段:

    在 PostView.ascx 顯示推文按鈕的片段[copy code]
        <%        Regex _stripHTML = new Regex("<[^>]*>", RegexOptions.Compiled);        string PostTextContent = _stripHTML.Replace(Post.Content, "");        int maxLength = 70;                    string EncodedAbsoluteLink = Page.Server.UrlEncode(Post.AbsoluteLink.ToString());        string EncodedPostTitle = Page.Server.UrlEncode(Post.Title);        string EncodedPostBody = Page.Server.UrlEncode((PostTextContent.Length > maxLength) ? (PostTextContent.Substring(0, maxLength) + "...") : (PostTextContent));        string TagsQueryString = "";        foreach (BlogEngine.Core.Category cat in Post.Categories)        {            TagsQueryString += string.Format("&tags[]=" + Page.Server.UrlEncode(cat.Title));        }    %>        <IFRAME	width=60 	height=55	marginWidth=0 	marginHeight=0 	frameBorder=0 	scrolling=no 	src="http://funp.com/tools/buttoniframe.php?url=<%=EncodedAbsoluteLink %>&s=1">    </IFRAME>
    
       1:    <%
    
       2:        Regex _stripHTML = new Regex("<[^>]*>", RegexOptions.Compiled);
    
       3:        string PostTextContent = _stripHTML.Replace(Post.Content, "");
    
       4:        int maxLength = 70;
    
       5:        string EncodedAbsoluteLink = Page.Server.UrlEncode(Post.AbsoluteLink.ToString());
    
       6:        string EncodedPostTitle = Page.Server.UrlEncode(Post.Title);
    
       7:        string EncodedPostBody = Page.Server.UrlEncode((PostTextContent.Length > maxLength) ? (PostTextContent.Substring(0, maxLength) + "...") : (PostTextContent));
    
       8:        string TagsQueryString = "";
    
       9:        foreach (BlogEngine.Core.Category cat in Post.Categories)
    
      10:        {
    
      11:            TagsQueryString += string.Format("&tags[]=" + Page.Server.UrlEncode(cat.Title));
    
      12:        }
    
      13:    %>
    
      14:    <IFRAME
    
      15:  width=60 
    
      16:  height=55
    
      17:  marginWidth=0 
    
      18:  marginHeight=0 
    
      19:  frameBorder=0 
    
      20:  scrolling=no 
    
      21:  src="http://funp.com/tools/buttoniframe.php?url=<%=EncodedAbsoluteLink %>&s=1">
    
      22:    </IFRAME>
    

     

    這是推文的部份,如果要張貼的話就不一樣了,要放的是把文章的預設資訊都帶過去,免的到時要重新輸入一次... 這部份的 CODE 比較囉唆,不過產生出來的 CODE 比較單純,就是個 <A> LINK 而以,不過因為帶的資訊比較多,所以部份 CODE 是由上面的 CODE 事先產生好,這裡才拿來用的:

    產生推文按鈕的部份[copy code]
       1:    <a href="http://funp.com/push/submit/add.php?url=<%=EncodedAbsoluteLink %>&s=<%=EncodedPostTitle %>&t=<%=EncodedPostBody %><%=TagsQueryString %>&via=tools" title="貼到funP">
       2:  <img src="http://funp.com/tools/images/post_03.gif" border="0"/>
       3:    </a>

     

     

     

     

    果然效果好多了,也不會再碰到版面掛掉等等鳥問題,只不過載入 [封存] 頁面時,一次四五百個 <IFRAME> 同時在跑,IE也是跑的很吃力....

    image

     

     

     

    同樣的技巧也拿來修改 ~/archive.aspx 這頁。這頁原本是把所有的文章按照分類一篇一篇列出來,捨棄原有的 RATING 機制不用,直接用推文的機制取代。因此這頁原本顯示 RATING 分數的地方就被我改成推推王的推薦次術了。我的文章有兩百多篇,出現過的地方都列一次,加一加總共會出現近五百個推文按鈕 @_@,自然也不可能用原本官方的作法產生按鈕,直接用上面挖出來的方法,修改 archive.aspx.cs:

    ~/archive.aspx.cs 顯示推文按鈕的片段程式[copy code]
              if (BlogSettings.Instance.EnableRating)          {              HtmlTableCell rating = new HtmlTableCell();              rating.InnerHtml = string.Format(                @"<IFRAME   marginWidth=0   marginHeight=0   src='http://funp.com/tools/buttoniframe.php?url={0}&amp;s=12'   frameBorder=0   width=80   scrolling=no   height=15></IFRAME>",                 (post.AbsoluteLink.ToString()));              rating.Attributes.Add("class", "rating");              row.Cells.Add(rating);          }
    
       1:            if (BlogSettings.Instance.EnableRating)
    
       2:            {
    
       3:                HtmlTableCell rating = new HtmlTableCell();
    
       4:                rating.InnerHtml = string.Format(
    
       5:                  @"
    
       6:  <IFRAME 
    
       7:    marginWidth=0 
    
       8:    marginHeight=0 
    
       9:    src='http://funp.com/tools/buttoniframe.php?url={0}&amp;s=12' 
    
      10:    frameBorder=0 
    
      11:    width=80 
    
      12:    scrolling=no 
    
      13:    height=15>
    
      14:  </IFRAME>", 
    
      15:                  (post.AbsoluteLink.ToString()));
    
      16:                rating.Attributes.Add("class", "rating");
    
      17:                row.Cells.Add(rating);
    
      18:            }
    

     

    嗯,看起來效果好多了,至少我自己看起來順眼多了 :D

    下一篇預告一下,下一篇會推出我自己寫的 PostViewCounter Extension,主要就是拿來作每篇文章的點閱率。BlogEngine 沒內建,找來的現成的又不是很合用,索性就自己寫了一個,請期待續篇 :D

    2008/06/30 .NET ASP.NET BlogEngine.NET 有的沒的

  2. 很抱歉,本站不歡迎來自 [百度] (Baidu.com) 的訪客 !!

    沒什麼特別的,只是針對這次的盜文事件,我很不滿意百度 (Baidu.com) 的處理方式而以。

    幾個月前曾發生過 BLOGGER.COM 有人直接把我的文章一字不漏的貼在他的 BLOG 上,沒有標示文章出處,最後用我破破的英文跟 GOOGLE 反映之後,GOOGLE 立即作了處理,關閉那位使用者的網頁

    這次碰到類似的情況,有耐心的人就聽我講完這無聊的故事吧。無意間我在對岸的入口網站 [百度知道] (類似奇摩知識+的網站),發現有人拿我的文章,一字不漏的貼上去回答問題賺點數,一樣沒有標示文章出處,感到非常的不滿,馬上註冊了帳號,留下回應表示該文侵犯了我的權益,要求引用文章要註明出處,同時也跟站方反應了這個情況,要求站方作妥善的處理。

    原本以為事情會像上次一樣,跟 GOOGLE 一樣的處理方式結束。沒料到...

    1. 隔天,發現我的留言被刪了? 嗯... 再補一次。
    2. 跟站方反應的結果? 竟然說這個不尊重智財權的使用者沒有違反規定???? 所以不做任何處理。
    3. 另一方面,留言留了不斷的被刪除,到現在已經被刪了第五次了,第六次的留言不知道會留到什麼時後...。

    很無聊的戲就這樣一直演下去... 就是不斷的抗議又被刪除,跟站方反應卻又不理睬...。看來小蝦米是對抗不了大鯨魚的,也只能這樣了。其實我除了文章被盜貼之外沒有什麼具體的損失,就是心理很不爽而以,而更離譜的是百度站方處理的態度...。

    資訊隨手可得,不代表資訊是可以任意踐踏的。免費的資訊,不用付費不代表就不需要尊重,也許對岸還有很多使用者沒建立起這樣的觀念,但是百度站方的處理方式也令我跌破眼鏡,有這樣的站方難怪會縱容這樣的使用者... :@

    身為渺小不起眼 BLOG 主人,我也只能用消極的抗議,來表示我的不滿。除了寫這篇文章以外,也順帶來個 ASP.NET HttpModule 教學...。針對這次事件,我特地在本站加上了這個 HttpModule,只要查出使用者是透過任何由百度提供的 LINK 而連到本站的話,都會顯示這頁抗議的畫面,如下:

    image

    顯示了 60 秒抗議畫面後,就會自動進如原本要連結的頁面。在透過正規的管道而得不到妥善的處置,我也只能用消極的抗意來表達我的不滿。請看到的人留個 MESSAGE 支持一下吧,或是有推推王帳號的人也幫忙推一下,一起對不重視智慧財產權的百度表答不滿 & 抗議!

     

    抗議之餘,本站再怎樣也是討論進階 .NET 技術的網站,就拿這次的案例,看看這樣的 HttpModule 該怎麼處理! 未來如果你也不幸碰到這樣的事件 (最好不要碰到),可以拿出來用一用! 要替網站加上這樣的功能很簡單,只要在 Web.config 把你寫的 HttpModule 掛上就好。一旦掛上,所有針對這個網站的 Http Request 都會經過你的 HttpModule 處理,任何一個 LINK 都跑不掉!

     

    ASP.NET HttpModule開發範例: 把所有來自百度的使用者,引導到抗議的畫面![copy code]
    public class SiteBlockerHttpModule : IHttpModule{    public void Init(HttpApplication context)    {        context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);    }    void context_AuthenticateRequest(object sender, EventArgs e)    {        HttpApplication application = sender as HttpApplication;        string referer = application.Context.Request.ServerVariables["HTTP_REFERER"];        if (string.IsNullOrEmpty(referer) == false)        {            Uri refererURL = new Uri(referer);            if (refererURL.Host.ToUpperInvariant().Contains("BAIDU.COM") == true)            {                application.Context.Server.Transfer("~/Blogs/ShowBlockedMessage.aspx");            }        }    }}
    
       1:  public class SiteBlockerHttpModule : IHttpModule
    
       2:  {
    
       3:      public void Init(HttpApplication context)
    
       4:      {
    
       5:          context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
    
       6:      }
    
       7:   
    
       8:      void context_AuthenticateRequest(object sender, EventArgs e)
    
       9:      {
    
      10:          HttpApplication application = sender as HttpApplication;
    
      11:          string referer = application.Context.Request.ServerVariables["HTTP_REFERER"];
    
      12:   
    
      13:          if (string.IsNullOrEmpty(referer) == false)
    
      14:          {
    
      15:              Uri refererURL = new Uri(referer);
    
      16:              if (refererURL.Host.ToUpperInvariant().Contains("BAIDU.COM") == true)
    
      17:              {
    
      18:                  application.Context.Server.Transfer("~/Blogs/ShowBlockedMessage.aspx");
    
      19:              }
    
      20:          }
    
      21:      }
    
      22:  }
    

     

     

     

     

    --

    後記: 針對這次事件的記錄:

    1. 2008‎年‎6‎月‎22‎日, ‏‎下午 11:59:16,第一次張貼抗議的回應 
    2. 2008‎年‎6‎月‎23‎日, ‏‎上午 11:44:50,第二次張貼抗議的回應 
    3. 2008‎年‎6‎月‎23‎日, ‏‎下午 04:53:44,第三次張貼抗議的回應 
    4. ‎2008‎年‎6‎月‎23‎日, ‏‎下午 11:32:05,第四次張貼抗議的回應 
    5. ‎2008‎年‎6‎月‎25‎日, ‏‎下午 08:08:14,第五次張貼抗議的回應 
    6. 2008‎年‎6‎月‎26‎日, ‏‎下午 07:26:36,第六次張貼抗議的回應 
    7. ‎2008‎年‎6‎月‎28‎日, ‏‎上午 12:58:54,百度站方的回應
       

    2008/06/28 技術隨筆 有的沒的 火大

  3. Bot Checker 回來了!

    哈哈,終於加回來了 :D

     

    為什麼原本在 CS 上很簡單就加上去的 Bot Checker, 在 BE 上弄到現在才好? 原因只有一個,就是 BE 在張貼回應時用了不少 AJAX 的機制,變的要插一段 CODE 進去要追半天 @_@...

    很諷刺的是,AJAX 其實是 Community Server 用的比較兇,到處都要來一下... 反而 BlogEngine.NET 就中規中舉多了,很多地方就都乖乖的用 PostBack.. 唯讀回應的地方很突兀,感覺好像是特別要現一下回應的那個 TEXT EDITOR 還有 BBCODE 預覽的樣子... 那邊的 CODE 弄的實在是有點亂...

    也是之前幾次都沒認真追啦... 追到一半覺的煩就去逛網站了,哈哈... 今天狠下心把它解決掉了。只不過卡著 AJAX,又不想跟它奮鬥,所以有些地方就偷懶混過去了。什麼意思? 意思就是攤開 HTML 原始碼,這個 Bot Checker 也是防不了 Bot 啦,不過我就賭我的站還有我的 Bot Checker 沒大到有人願意寫 Bot 來攻擊我.. 哈哈.. 來看看效果:

    image

     

    當然,張貼出去的回應也會帶著 Bot Checker 的問題。只不過礙於 AJAX,一堆東西被迫要移到 CLIENT SIDE 來處理,這邊就偷懶,題目產生完就先填到 [評論] 欄了,各位在填回應時,不爽附上 "芭樂雞萬歲" 的話,還是可以把它刪掉...

     

    image

     

     

    --
    題外話,在追 BE 的程式碼的過程中,發現 BE 也有 CAPTCHA ? 不過還真的找不到怎麼把它打開... 有興趣用 BlogEngine.NET 又想用正統的 CAPTCHA 驗證的人可以試看看

    2008/06/25 .NET ASP.NET BlogEngine.NET 作品集 有的沒的

  4. 又被盜文了... :@

    該說人紅嘛? 看起來也不是,就是正好我貼的文章又被盜用了。除了上次 [這篇文章] 被盜貼到 BLOGGER 上之外,這次又發現另一篇被盜貼了。這次被盜貼的是我之前發表一篇 [原來 System.Net.Mail 也會有 Bug ] 的文章,說明我碰到 Microsoft .NET Framework 的 BUG 及我挖掘問題真相的技術文章...。

     

     

    image

    這次無意間用 GOOGLE 找相關文章找到的,兩篇都是在對岸的網站,一個是照文全貼,唯獨 "忘" 了註明出處。至少他還很忠實原味,範例程式中出現吳小皮吳小妹,還有他們在 chicken-house.net 的 EMAIL 都沒有改掉... 只是把它去頭去尾,翻成簡體中文就貼了上去..

     

     

     

    image

    另一篇不是貼在他自己的 BLOG,而是貼在 "百度知道" 上面賺點數... 什麼是 "百度知道" ? 就是有點像台灣的奇魔知識+,在上面回答解決問題,賺分數用的。有人碰到了同一個 BUG 解不出來,對岸也有同胞很熱心的找到我的文章可以解決他的問題,就貼了上去... 一樣不小心 "忘" 了註明出處...。這位對岸同胞比較好一點,除了翻成簡體中文之外,其它就都照貼,沒有幫我去頭去尾...。

     

    其實寫 BLOG 就是要分享用的,也不要求說要啥版稅或費用的。轉貼文章很歡迎,不過就是要求個 "尊重" 而以。同一篇文章也有幫到一位對岸同胞,他有在我 BLOG 留回應,其實這樣很好啊,幫到他我自己也很有成就感。不過這樣不註明出處的轉貼,我分享的動力完全被破壞掉了,只是簡單的尊重這麼難嘛?

     

    這兩個地方我都留了 comment,請該篇文章的主人註明出處,也不曉得有沒有用。我把他們的網站都抓圖下來了,想看的請自己到圖中挖網址,我就不想貼在文章內了。不想因為我貼了這篇又增加他們網站的點擊率,我也不想要有我這邊 "引用" 他們文章的記錄,算是我無言的抗議吧。

     

    沒事,發發牢騷而以。希望以後不要再有這種狀況發生了...。

    2008/06/23 .NET 作品集 技術隨筆 有的沒的 火大

  5. 利用 WPF 讀取 CANON (.CR2) 的 EXIF 及縮圖 (C# 範例程式說明)

    因為有人留話在詢問怎麼利用 WPF 處理這些動作,而這些又都是 Microsoft 文件及範例沒有寫的很清楚的部份... 有些範例有提到,但是 Microsoft 內建的 CODEC 正常,3RD PARTY 的 CODEC (像我碰到的 Canon Raw Codec) 就搞半天也搞不出來...。因為之前寫 MediaFiler 正好都碰過這些釘子,就順手寫起來以免以後又忘掉.. :P

     

    1. 讀取 Metadata

    講 METADATA 也許會有些人一頭霧水,講 EXIF 搞不好還比較多人知道... EXIF 的規格有點混亂,就像 RSS 一樣,有好幾個派係,Microsoft 乾脆在 WPF 裡跳出來直接稱它做 METADATA,也搭配了 METADATA QUERY LANGUAGE 來避開像 EXIF 一堆必需事先定義好 PROPERTY NAME 的麻煩事...

    所以所有的動作都集中在兩件事上面,一個是怎麼操作 METADATA ? 另一個是我要的資料到底藏在那? (你得知道什麼樣的 QUERY 才抓的到你要的資料?)

    先看簡單的,貼段 SAMPLE CODE 就搞定了..

    SAMPLE 1. 如何讀取 METADATA ?[copy code]
                    string srcFile = @"SampleRawFile.CR2";                //                //  sample 1. read metadata                //                FileStream fs = File.OpenRead(srcFile);                BitmapMetadata metadata = BitmapDecoder.Create(                    fs,                    BitmapCreateOptions.None,                    BitmapCacheOption.None).Frames[0].Metadata as BitmapMetadata;                foreach (string query in queries)                {                    object result = metadata.GetQuery(query);                    Console.WriteLine("query[{0}]: {1}", query, result);                }                fs.Close();
    
       1:  string srcFile = @"SampleRawFile.CR2";
    
       2:  //
    
       3:  //  sample 1. read metadata
    
       4:  //
    
       5:  FileStream fs = File.OpenRead(srcFile);
    
       6:  BitmapMetadata metadata = BitmapDecoder.Create(
    
       7:      fs,
    
       8:      BitmapCreateOptions.None,
    
       9:      BitmapCacheOption.None).Frames[0].Metadata as BitmapMetadata;
    
      10:  foreach (string query in queries)
    
      11:  {
    
      12:      object result = metadata.GetQuery(query);
    
      13:      Console.WriteLine("query[{0}]: {1}", query, result);
    
      14:  }
    
      15:  fs.Close();
    

     

    扣掉 COMMENTS 只有12行... 哈哈,看起來好像也沒什麼好講的,關鍵就只有一個,STREAM別太早關掉...。以前我就吃過虧,試了好久才知道原來是我太雞婆,在第10行之前就把 fs 給關了,後面通通讀不出來...

    不過關鍵的 queries (型別是 string[] ),內容到底是啥? 老實說我也不是很清楚 :P 先貼一下建立這字串陣列的 CODE:

    建立 CR2 支援的 METADATA QUERY[copy code]
            private static string[] queries = new string[] {            "/ifd/{ushort=256}",            "/ifd/{ushort=257}",            "/ifd/{ushort=258}",            "/ifd/{ushort=259}",            "/ifd/{ushort=262}",            "/ifd/{ushort=270}",            "/ifd/{ushort=271}",            "/ifd/{ushort=272}",            "/ifd/{ushort=273}",            "/ifd/{ushort=274}",            "/ifd/{ushort=277}",            "/ifd/{ushort=278}",            "/ifd/{ushort=279}",            "/ifd/{ushort=282}",            "/ifd/{ushort=283}",            "/ifd/{ushort=284}",            "/ifd/{ushort=296}",            "/ifd/{ushort=306}",            "/ifd/{ushort=315}",            "/ifd/{ushort=34665}/{ushort=33434}",            "/ifd/{ushort=34665}/{ushort=33437}",            "/ifd/{ushort=34665}/{ushort=34855}",            "/ifd/{ushort=34665}/{ushort=36864}",            "/ifd/{ushort=34665}/{ushort=36868}",            "/ifd/{ushort=34665}/{ushort=37377}",            "/ifd/{ushort=34665}/{ushort=37378}",            "/ifd/{ushort=34665}/{ushort=37380}",            "/ifd/{ushort=34665}/{ushort=37386}",            "/ifd/{ushort=34665}/{ushort=37500}",            "/ifd/{ushort=34665}/{ushort=37510}",            "/ifd/{ushort=34665}/{ushort=40960}",            "/ifd/{ushort=34665}/{ushort=40961}",            "/ifd/{ushort=34665}/{ushort=41486}",            "/ifd/{ushort=34665}/{ushort=41487}",            "/ifd/{ushort=34665}/{ushort=41488}",            "/ifd/{ushort=34665}/{ushort=41728}",            "/ifd/{ushort=34665}/{ushort=41985}",            "/ifd/{ushort=34665}/{ushort=41986}",            "/ifd/{ushort=34665}/{ushort=41987}",            "/ifd/{ushort=34665}/{ushort=41988}",            "/ifd/{ushort=34665}/{ushort=41990}",            "/ifd/{ushort=34665}/OffsetSchema:offset"        };
    
       1:  private static string[] queries = new string[] {
    
       2:      "/ifd/{ushort=256}",
    
       3:      "/ifd/{ushort=257}",
    
       4:      "/ifd/{ushort=258}",
    
       5:      "/ifd/{ushort=259}",
    
       6:      "/ifd/{ushort=262}",
    
       7:      "/ifd/{ushort=270}",
    
       8:      "/ifd/{ushort=271}",
    
       9:      "/ifd/{ushort=272}",
    
      10:      "/ifd/{ushort=273}",
    
      11:      "/ifd/{ushort=274}",
    
      12:      "/ifd/{ushort=277}",
    
      13:      "/ifd/{ushort=278}",
    
      14:      "/ifd/{ushort=279}",
    
      15:      "/ifd/{ushort=282}",
    
      16:      "/ifd/{ushort=283}",
    
      17:      "/ifd/{ushort=284}",
    
      18:      "/ifd/{ushort=296}",
    
      19:      "/ifd/{ushort=306}",
    
      20:      "/ifd/{ushort=315}",
    
      21:      "/ifd/{ushort=34665}/{ushort=33434}",
    
      22:      "/ifd/{ushort=34665}/{ushort=33437}",
    
      23:      "/ifd/{ushort=34665}/{ushort=34855}",
    
      24:      "/ifd/{ushort=34665}/{ushort=36864}",
    
      25:      "/ifd/{ushort=34665}/{ushort=36868}",
    
      26:      "/ifd/{ushort=34665}/{ushort=37377}",
    
      27:      "/ifd/{ushort=34665}/{ushort=37378}",
    
      28:      "/ifd/{ushort=34665}/{ushort=37380}",
    
      29:      "/ifd/{ushort=34665}/{ushort=37386}",
    
      30:      "/ifd/{ushort=34665}/{ushort=37500}",
    
      31:      "/ifd/{ushort=34665}/{ushort=37510}",
    
      32:      "/ifd/{ushort=34665}/{ushort=40960}",
    
      33:      "/ifd/{ushort=34665}/{ushort=40961}",
    
      34:      "/ifd/{ushort=34665}/{ushort=41486}",
    
      35:      "/ifd/{ushort=34665}/{ushort=41487}",
    
      36:      "/ifd/{ushort=34665}/{ushort=41488}",
    
      37:      "/ifd/{ushort=34665}/{ushort=41728}",
    
      38:      "/ifd/{ushort=34665}/{ushort=41985}",
    
      39:      "/ifd/{ushort=34665}/{ushort=41986}",
    
      40:      "/ifd/{ushort=34665}/{ushort=41987}",
    
      41:      "/ifd/{ushort=34665}/{ushort=41988}",
    
      42:      "/ifd/{ushort=34665}/{ushort=41990}",
    
      43:      "/ifd/{ushort=34665}/OffsetSchema:offset"
    
      44:  };
    

     

    因為我之前處理的目標,是在做轉檔的動作,同時要忠實的把 METADATA 也複製過去,因此得到這列表對我來說很重要,但是搞懂每個項目的意義是啥我就不管了,先列出來再說... 需要的人請自行判斷....

     

     

    2. 建立縮圖

    WPF 架構滿分,但是效能多少會打折扣。JPEG實在看不大出來,拿CANON的CODEC來看就很清楚了。如果是以 .CR2 -> .JPG,不作大小的縮放,CANON 自家的 DPP 大概要廿幾秒,CANON自家寫給 WPF 用的 CODEC 搭配 .NET 的 WPF 程式,則要 60 秒左右,測試的機器就我這台 Vista + 4GB RAM, CPU 是 Core2Duo E6300...

    速度實在是不快,也不是用 .NET 寫效能太糟的原因,因為 VISTA 內建的秀圖程式也是靠同一個 CODEC,效能也差不多... 不過 CODEC 的架構設計的很好,如果我要的只是縮圖,那就不一樣了... 來看第二個範例:

    SAMPLE 2. 建立原圖 (.CR2) 1/10 大小的 JPEG 縮圖[copy code]
                    Stopwatch timer = new Stopwatch();                timer.Start();                FileStream fs = File.OpenRead(srcFile);                FileStream fs2 = File.OpenWrite(Path.ChangeExtension(srcFile, ".jpg"));                BitmapDecoder source = BitmapDecoder.Create(                    fs,                    BitmapCreateOptions.None,                    BitmapCacheOption.None);                JpegBitmapEncoder target = new JpegBitmapEncoder();                target.Frames.Add(BitmapFrame.Create(                    new TransformedBitmap(source.Frames[0], new ScaleTransform(0.1, 0.1)),                    null,                    null,                    null));                target.QualityLevel = 90;                target.Save(fs2);                fs.Close();                fs2.Close();                timer.Stop();                Console.WriteLine("Create 0.1x thumbnail: {0} ms.", timer.ElapsedMilliseconds);
    
       1:  Stopwatch timer = new Stopwatch();
    
       2:  timer.Start();
    
       3:  FileStream fs = File.OpenRead(srcFile);
    
       4:  FileStream fs2 = File.OpenWrite(Path.ChangeExtension(srcFile, ".jpg"));
    
       5:  BitmapDecoder source = BitmapDecoder.Create(
    
       6:      fs,
    
       7:      BitmapCreateOptions.None,
    
       8:      BitmapCacheOption.None);
    
       9:  JpegBitmapEncoder target = new JpegBitmapEncoder();
    
      10:  target.Frames.Add(BitmapFrame.Create(
    
      11:      new TransformedBitmap(source.Frames[0], new ScaleTransform(0.1, 0.1)),
    
      12:      null,
    
      13:      null,
    
      14:      null));
    
      15:  target.QualityLevel = 90;
    
      16:  target.Save(fs2);
    
      17:  fs.Close();
    
      18:  fs2.Close();
    
      19:  timer.Stop();
    
      20:  Console.WriteLine("Create 0.1x thumbnail: {0} ms.", timer.ElapsedMilliseconds);
    

     

    範例程式也很簡單,扣掉計時的程式碼只有 16 行... 裡面兩個關鍵的參數,分別為第 11 行, 0.1 代表 Scale Transform 用的縮放比例,0.1 就是只要 1/10 的大小。如果你想要固定尺寸的縮圖,得先自行計算出這個 SCALE 的值。要單純的轉檔,就填 1.0 就好。

    另一個參數是第15行的 90,它是指存成 JPEG 時要使用的 QUALITY,100 最好,越低失真越嚴重,但是相對的檔案大小也會大幅下降。一般用的 QUALITY 大約都在 75% ~ 90%,其實縮圖 75% 就夠了,反正看不大出來 (H)

     

    我用 CANON G9 拍出來的 RAW (4000 X 3000),存成 400x300 JPEG,約要花費 1.5 秒,比原圖尺寸的 80 秒差太多了,可見 CODEC 一定針對這種需求作過最佳化,會有效避開縮圖時不必讀取的部份資料... 以加快速度。

    同樣程式改成不縮圖看看,如果不縮圖的話,ScaleTransform也可以省了,直接把 Frame 加進去就好,原程式的 line 10 ~ line 14,換成這段:

    不縮圖的替代程式碼[copy code]
                    target.Frames.Add(source.Frames[0]);
    
       1:  target.Frames.Add(source.Frames[0]);
    

     

    嘖,只有一行也在貼... 沒錯,就是這樣而以。跑出來的時間約為 65 秒...

    這部份我就沒仔細的拿 DPP 比較過了。不過當你縮圖的尺寸降低時,的確是能有效的加快速度。不過如果對於 ASP.NET WEB 應用程式來看,1.5 秒還是太慢了 @_@,十個人連上來就會想哭了,怎麼辦? 只好善用 CACHE,不然就買本六月號 RUN PC 看看我投的那篇文章... 哈哈...

     

     

    ----

    PS: 這篇是寫給對岸的那位愛賞鳥的網友看的,希望有解決到你的問題。完成後也記得給我欣賞一下你們的攝影作品 :D

    PS2: 範例程式放在這裡,請自行下載。圖檔請放同目錄的 SampleRawFile.CR2

    PS3: 家裡大人不准我買 PS3 ...

    2008/06/23 .NET WPF 技術隨筆 有的沒的