14 October 2013

ภาพ Vector ขยายยังไงภาพก็ไม่แตกกกกกก

Updated on

        วันนี้เป็นเรื่องผสมโรงระหว่างโค๊ดกับการออกแบบเสียหน่อยนะ เพราะเป็นเรื่องของการออกแบบนั่นแหละ แต่มีโค๊ดมาเกี่ยวข้อง โดยบทความนี้ขอพูดเรื่องภาพเวคเตอร์กับแอปพลิเคชันเสียหน่อย


        สำหรับผู้ที่หลงเข้ามาอ่านที่ไม่เคยทำงานเกี่ยวกับกราฟฟิคมาก่อนก็คงจะไม่ค่อยรู้จักกันมากนักว่าภาพเวคเตอร์มันคืออะไร พิเศษยังไง 

        ภาพที่ใช้งานกันบนคอมนั้นจะแบ่งออกเป็นสองประเภทใหญ่ๆด้วยกันดังนี้


Bitmap

        ภาพประเภท Bitmap (เรียกอีกอย่างว่า Raster) เป็นภาพที่เกิดจากจุดหลายๆจุดที่มีสีต่างกันมาเรียงกันที่เรียกกันว่าจุดพิกเซลนี่แหละ จนเป็นภาพหนึ่งภาพ ภาพประเภทนี้จะพบเห็นได้ทั่วไปอยู่แล้วบนคอม ไม่ว่าจะรูปถ่าย รูปจากอินเตอร์เนต ล้วนก็เป็น Bitmap ทั้งนั้น จุดสังเกตที่สำคัญของภาพ Bitmap เลยก็คือ เมื่อขยายภาพจนถึงระยะนึงจะเห็นว่าภาพแตก ภาพที่มีขนาดเล็กขยายนิดขยายหน่อยก็แตกแล้ว ส่วนภาพที่มีขนาดใหญ่จะขยายได้มากกว่าภาพเล็ก แต่เมื่อขยายไปเยอะๆภาพก็แตกอยู่ดีนั่นแหละ



Vector

        ภาพเวคเตอร์จะเป็นภาพที่เกิดจากการคำนวณพิกัด XY บนระนาบ 2 มิติ เส้นทุกเส้น สีทุกสีเกิดจากการคำนวณทางคอมพิวเตอร์แล้วแสดงออกมาเป็นภาพ ไม่ได้เกิดจากการนำพิกเซลหลายๆจุดมาเรียงต่อๆกันแบบ Bitmap สำหรับภาพแบบ Vector จะพบได้ในงานกราฟฟิคบนคอมพิวเตอร์เท่านั้น มีคุณสมบัติพิเศษตรงที่ขยายเท่าไรภาพก็ไม่แตก เพราะเกิดจากการคำนวณจากคอมพิวเตอร์แล้ววาดใหม่ทุกครั้ง ดังนั้นไม่ว่าจะขยายมากเท่าไร มากเท่าไร ภาพก็จะคำนวณแล้ววาดขึ้นใหม่ ดังนั้นจึงเป็นที่นิยมกับงานออกแบบกราฟฟิค เพราะนำไปใช้งานได้สะดวก แต่ลูกเล่นในการตกแต่งเอฟเฟคจะยากกว่า ซึ่งต้องใช้ฝีมือพอสมควร



        สำหรับภาพ Vector จะพบกันได้ในไฟล์ที่มีนามสกุลเป็น .ai เป็นต้น จริงๆแล้วก็ยังมีอีกหลายๆไฟล์เช่นกัน แต่งานส่วนมากจะนิยมเป็นไฟล์ .ai ซึ่งงาน Vector จะนำไปใช้งานก็จะแปลงให้เป็นภาพแบบ Bitmap แทน เมื่อแปลงเป็นภาพแบบ Bitmap แล้วก็จะพบกับปัญหาขยายแล้วภาพแตก

        ดังนั้นจึงนิยมแปลงเป็นภาพ Bitmap ให้มีขนาดพอเหมาะสมกับงาน เวลาใช้งานกับแอปพลิเคชันแอนดรอยด์ก็ทำออกมาตามหน้าจอแต่ละขนาด ก็จะหมายความว่าถึงจะทำภาพ Vector แล้ว แต่สุดท้ายก็ใช้เป็น Bitmap แถมต้องทำหลายขนาดอีกต่างหาก ทำให้ภาพหนึ่งภาพมีหลายไฟล์

        โดยที่ระบบบนแอนดรอยด์นั้นจะรองรับไฟล์ภาพดังนี้ 

                        JPEG, GIF, PNG, BMP และ WEBP (เฉพาะ 4.0 ขึ้นไป) 

        จะเห็นว่าไฟล์เหล่านี้เป็นไฟล์ภาพแบบ Bitmap ทั้งนั้นเลย


        เป็นไปได้มั้ยที่จะเอาไฟล์ภาพแบบ Vector มาใช้งานเลย?

        ถึงแม้ระบบจะไม่รองรับโดยตรง แต่ก็ใช่ว่าจะทำไม่ได้ เพราะภาพ Vector เป็นไฟล์สำหรับคำนวณข้อมูลให้ออกมาเป็นภาพ ดังนั้นก็จึงเป็นไปได้ ถ้าเขียนโค๊ดเพื่ออ่านข้อมูลของไฟล์เหล่านั้น แล้ววาดเป็นภาพ Bitmap ก็สามารถเอาไปใช้งานได้แล้ว

        แล้ว? เขียนโค๊ดยังไงล่ะ? ยากมั้ย?

        อะไรที่ยากๆก็ใช้ไลบรารีไปเลย ไม่ต้องมานั่งเขียนเองให้วุ่นวาย เจ้าของบล็อกมีไลบรารีสำหรับเรื่องนี้โดยเฉพาะ ให้ลองใช้กันดู เป็นไลบรารีที่ชื่อว่า svg-android สามารถเข้าไปดูกันได้ที่ https://code.google.com/p/svg-android/

        ต่อไปจะเป็นการนำไลบรารีมาใช้งานกันเลยละกันนะ ให้ดาวน์โหลดไลบรารีมาก่อน ดาวน์โหลดได้จาก https://github.com/pents90/svg-android




        เมื่อได้ไฟล์มาแล้วก็ให้ทำการแตกไฟล์ออกมาซะ




        จากนั้นไปที่ Eclipse แล้วเลือกที่ File > Import




        ที่หน้าต่าง Import ให้เลือกที่ Android > Existing Android Code Into Workspace




        ที่หน้าต่างเลือกโปรเจคที่จะ Import ให้กดที่ปุ่ม Browse...




        ทำการเลือกโฟลเดอร์ไปที่ไลบรารีของ svg-android




        เมื่อเลือกโฟลเดอร์แล้วให้เลือก Import แค่ svgandroid เท่านั้น (เลือกดูที่ช่อง New project name) และติ๊กเลือกที่ช่อง Copy project into workspace แล้วกดปุ่ม Finish




        เท่านี้ไลบรารีของ svg-android ก็เข้ามาอยู่ใน Workspace แล้ว




        จากนั้นให้สร้างโปรเจคมาซักโปรเจคเพื่อทดสอบดู เจ้าของบล็อกจะสร้างโปรเจคชื่อว่า SVGSample ละกัน




        คลิกขวาที่โฟลเดอร์โปรเจคที่พึ่งสร้างมา แล้วเลือก Properties




        ที่หน้าต่าง Properties ช่องซ้ายมือให้เลือกที่ Android จากนั้นที่ช่องขวามือล่างสุดให้กดปุ่ม Add... เพื่อทำการเพิ่มไลบรารี svg-android ให้กับโปรเจคนี้




        จากนั้นให้เลือกโปรเจค svgandroid แล้วกดปุ่ม OK




        เมื่อกำหนดเสร็จแล้วก็กดปุ่ม OK เพื่อปิดหน้าต่าง Properties ได้เลย




        เมื่อจัดการกับโปรเจคในเบื้องต้นแล้ว ให้หยุดเรื่อง Eclipse ไว้ก่อน เพราะต้องหาภาพ Vector มาใช้งาน จะเห็นว่าไลบรารีชื่อ svgandroid ดังนั้นก็พอจะเดาได้ว่าไฟล์ Vector ที่ว่าต้องเป็นไฟล์นามสกุล SVG

        SVG เป็นไฟล์ภาพแบบ Vector อีกนามสกุลหนึ่งที่ไม่ค่อยจะได้เห็นกันนัก ใช้กันในเฉพาะบางงานเท่านั้น เริ่มใช้งานกันตั้งแต่เมื่อปี 2001


        สามารถไปอ่านเสริมความรู้กันเล่นๆได้ที่ Scalable Vector Graphics [Wikipedia]


        สำหรับการสร้างภาพ Vector อันนี้แล้วแต่ผู้ที่หลงเข้ามาอ่านเลย ถ้ายังไม่เคยทำมาก่อนหรือทำไม่เป็น (ก็เหมือนกันนี่หว่า) ก็ลองใช้โปรแกรม Adobe Illustrator แบบเจ้าของบล็อกก็ได้ โปรแกรมหากินสำหรับเหล่ากราฟฟิคดีไซนเนอร์หลายๆคน (เจ้าของบล็อกใช้เป็น CS6 นะ ถ้าคนละเวอร์ชันก็จะต่างกันหน่อย)


** ข้อสำคัญสำหรับการทำภาพ Vector **

        ถึงแม้ว่าจะนำภาพ Bitmap มาใส่ในงาน Vector ได้ แต่ภาพ Bitmap ก็ไม่ได้แปลงเป็น Vector ให้เลยนะ อย่าเข้าใจผิดแล้วเอาภาพ Bitmap มาลากใส่งาน Vector เพื่อหวังว่า Bitmap มันจะเป็น Vector เวลาขยายภาพจะไม่แตก (ถ้าทำแบบนั้นได้จริงๆป่านนี้ภาพถ่ายก็ไม่ต้องมีความละเอียดสูงกันแล้ว)

        สำหรับภาพที่จะใช้เป็นตัวอย่างก็จะใช้เป็นภาพนี้นะครับ


        สังเกตดีๆจะเห็นว่าภาพมันคมชัดมากไม่มีรอยแตกของภาพเลย อย่าลืมว่ามันคือภาพ Vector และสังเกตดีๆเจ้าของบล็อกไม่ได้วาดภาพใหญ่ แต่เจ้าของบล็อกวาดภาพเล็กพอประมาณแต่ขยาย 600%

        สำหรับวิธีการใช้งาน Adobe Illustrator ก็ไปหัดใช้กันเอาเองนะ เพราะไม่ใช้หน้าที่ของเจ้าของบล็อกที่ต้องมาสอนถึงวิธีใช้โปรแกรมนี้ หรือถ้ามีโปรแกรมที่ถนัดอยู่แล้ว ขอแค่เซฟเป็นไฟล์ SVG ได้ ก็ใช้ได้แล้ว

        สำหรับ Adobe Illustrator เจ้าของบล็อกตั้งค่าตอนเซฟตามปกติ ที่โปรแกรมตั้งค่ามาให้ดังนี้นะ เผื่อผู้ที่หลงเข้ามาอ่านที่อยากรู้


        (จริงๆไม่มีผลหรอก ใช้ได้เหมือนกันแหละน่า)

        เจ้าของบล็อกจะเซฟเป็นไฟล์ชื่อว่า sample_image.svg นะ เวลาเอาไปใช้ใน Eclipse ไม่ได้ใส่ไว้ในโฟลเดอร์ drawable นะ เนื่องจาก drawable ไม่ได้รองรับไฟล์ .svg อยู่แล้ว ให้สร้างโฟลเดอร์ raw ขึ้นมาในโฟลเดอร์ res (res/raw) แล้วเอาไฟล์ภาพ Vector ไปไว้ในนั้นแทน



        ทีนี้มาดูที่โค๊ดกันเลยนะ ก่อนอื่นไปจัดหน้า Layout กันก่อน เจ้าของบล็อกจัดหน้า Layout ให้มีแค่ Image View กลางจอ

main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" > <ImageView android:id="@+id/imageView1" android:layout_width="500dp" android:layout_height="500dp" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:src="@drawable/ic_launcher" /> </RelativeLayout>

        กำหนดให้แสดงภาพ ic_launcher หรือภาพไอคอนไปก่อน เพราะว่าไฟล์ภาพ SVG ยังไม่สามารถแสดงได้ทันทีใน Layout ส่วน Image View ขอตั้งชื่อไอดีง่ายๆว่า imageView1

        ทีนี้มาดูการใช้งานโค๊ดของไลบรารี svg-android กันต่อ สำหรับการใช้งานไลบรารีตัวนี้จะง่ายมากมาย
SVG svg = SVGParser.getSVGFromResource(getResources(), R.raw.sample_image); ImageView iv = (ImageView)findViewById(R.id.imageView1); iv.setImageDrawable(svg.createPictureDrawable());
 
        คงเดาโค๊ดกันได้ไม่ยาก คือดึงภาพจาก res/raw/sample_image.svg มากำหนดค่าให้กับ Object ของคลาส SVG โดยใช้ชื่อว่า svg จากนั้นก็ประกาศและกำหนดค่าให้กับ Image View ใช้ชื่อว่า iv แล้วกำหนดภาพให้กับ iv โดยดึงภาพเป็นคลาส Drawable จาก svg

        นอกจากเก็บภาพไว้ใน res/raw แล้ว เก็บในโฟลเดอร์ assets ก็ได้นะ
SVG svg = SVGParser.getSVGFromAsset(getAssets(), "sample_image.svg");

        ในกรณีที่ดึงภาพผ่านคลาส InputStream
InputStream input = กำหนดค่า; SVG svg = SVGParser.getSVGFromInputStream(input);


        สรุปโค๊ดใน Main.java ก็จะมีดังนี้

Main.java
package app.akexorcist.svgsample; import com.larvalabs.svgandroid.SVG; import com.larvalabs.svgandroid.SVGParser; import android.os.Bundle; import android.app.Activity; import android.widget.ImageView; public class Main extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); SVG svg = SVGParser.getSVGFromResource(getResources() , R.raw.sample_image); ImageView iv = (ImageView)findViewById(R.id.imageView1); iv.setImageDrawable(svg.createPictureDrawable()); } }


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.akexorcist.svgsample" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="app.akexorcist.svgsample.Main" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

        พอลองทดสอบดูก็จะพบกับภาพ Vector แสดงบนหน้าจอแล้ว ไม่ว่าจะแสดงบนหน้าจอความละเอียดแค่ไหนภาพก็จะไม่แตก (เว้นแต่ว่าพวกเครื่องจอใหญ่ความละเอียดต่ำนะ อันนั้นเป็นที่เครื่อง)



        แต่ถ้าเกิดว่า ภาพดันไม่แสดงซะงั้นล่ะ?

        โค๊ดนี้จะใช้งานได้สำหรับแอนดรอยด์เวอร์ชันต่ำกว่า 2.3.7 ลงไป ซึ่งเวอร์ชันตั้งแต่ 3.0 ขึ้นไปจะไม่แสดงภาพให้เห็น เนื่องจากว่าแอนดรอยด์เวอร์ชัน 3.0 ขึ้นไปจะมีความสามารถ ในการนำ GPU มาช่วยประมวลผลในการแสดงภาพบน View เรียกกันว่า Hardware Acceleration ซึ่งไลบรารีตัวนี้ไม่ได้ทำให้รองรับ ดังนั้นเครื่องเวอร์ชันดังกล่าวจะไม่สามารถแสดงภาพได้ (แต่โค๊ดก็ไม่ได้เออเรอร์นะ แค่ภาพไม่ขึ้นเฉยๆ)

        วิธีแก้ก็จะมีอยู่ด้วยกันสองสามวิธี ถ้าง่ายสุดก็คือ ยกเลิกการใช้งาน Hardware Acceleration ซะ กำหนดใน Activity นี้ว่าไม่ใช้งาน Hardware Acceleration โดยกำหนดที่ AndroidManifest.xml ดังนี้
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.akexorcist.svgsample" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" android:hardwareAccelerated="false" > <activity android:name="app.akexorcist.svgsample.Main" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
        โดยเป็นการกำหนดว่าใน Main จะไม่ใช้ Hardware Acceleration ซึ่งจำมีผลแค่ Activity ที่กำหนดเท่านั้น Activity อื่นก็กำหนดแยกกันได้ แต่วิธีนี้จะทำให้ทั้ง Activity ไม่ใช้ Hardware Acceleration เลย

        ในกรณีที่อยากให้ Activity นั้นต้องการใช้งานละก็ ให้ไปกำหนดแค่ในโค๊ดแทนว่าให้เฉพาะ Image View ไม่ใช้งาน
SVG svg = SVGParser.getSVGFromResource(getResources(), R.raw.sample_image); ImageView iv = (ImageView)findViewById(R.id.imageView1); iv.setImageDrawable(svg.createPictureDrawable()); iv.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
 
        คำสั่งนี้จะเป็นการกำหนดแค่ที่ Image View แทน ส่วนการทำงานอื่นๆใน Activity นั้นๆ ก็จะใช้งานได้เหมือนเดิม

        แต่ปัญหาต่อมาคือ ไอคำสั่งนี้มันใช้กับ API ที่ต่ำกว่า 11 ไม่ได้ เพราะมันถูกเพิ่มเข้ามาใน Android 3.0 นั่นเอง ถ้าจะให้รองรับทั้งเวอร์ชันใหม่และเก่าด้วย ก็ให้เช็คเวอร์ชันของเครื่องก่อนว่าเวอร์ชันอะไร ถ้าต่ำกว่า API 10 ลงมาก็จะไม่ใช้คำสั่งนี้ แต่ถ้าสูงกว่านั้นก็จะให้ใช้คำสั่งนี้ ดังนี้
SVG svg = SVGParser.getSVGFromResource(getResources(), R.raw.sample_image); ImageView iv = (ImageView)findViewById(R.id.imageView1); iv.setImageDrawable(svg.createPictureDrawable()); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { iv.setLayerType(View.LAYER_TYPE_SOFTWARE, null); }
 
        ก็แค่ใช้ IF เช็คนั่นแหละว่าเวอร์ชันต่ำกว่า Honeycomb หรือไม่ โดยที่ Honeycomb คือเวอร์ชัน 3.0 หรือ API 11 นั่นเอง

        แต่ตอนที่พิมโค๊ดจะพบกับปัญหานี้ต่อ


        โปรแกรม Eclipse จะบอกว่าคำสั่งนี้อาจจะเกิดปัญหาได้ เพราะว่าโปรเจคได้ตั้งไว้ให้รันบนแอนดรอยด์ขั้นต่ำสุดคือ API 8 โดยทำงานที่ API 11 ขึ้นไป ดังนั้นบนเครื่องที่เป็น API 8-10 ก็จะทำให้คำสั่งนี้เกิดการเออเรอร์ขึ้นได้นั่นเอง

        แต่จากคำสั่งเจ้าของบล็อกใช้ IF เช็คแล้ว ดังนั้นคำสั่งนี้จึงไม่มีทางทำงานบน API 8 - 10 แน่นอน ก็ให้เลือก Disable Check เพื่อยกเลิกการเช็คเออเรอร์ตัวนี้ไป

        เท่านี้ก็เป็นอันเสร็จแล้วกับการนำภาพแบบ Vector มาใช้งานบนแอปพลิเคชันของผู้ที่หลงเข้ามาอ่าน ทำให้จัดการปัญหาเรื่องภาพแตกเมื่อขยายได้ดีนัก