Android Programjaim

Android, Flutter/Dart, Kotlin, Java, Unity, HarmonyOS

HexagonImageView in android

2014. november 02. 16:48 - lacas8282

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.widget.ImageView;

public class HexagonImageView extends ImageView {
    
    private Path hexagonPath;
    private Path hexagonBorderPath;
    private float radius;
    private Bitmap image;
    private int viewWidth;
    private int viewHeight;
    private Paint paint;
    private BitmapShader shader;
    private Paint paintBorder;
    private int borderWidth = 4;
    
    public HexagonImageView(Context context) {
        super(context);
        setup();
    }
    
    public HexagonImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setup();
    }
    
    public HexagonImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setup();
    }
    
    private void setup() {
        paint = new Paint();
        paint.setAntiAlias(true);

        paintBorder = new Paint();
        setBorderColor(Color.WHITE);
        paintBorder.setAntiAlias(true);
        this.setLayerType(LAYER_TYPE_SOFTWARE, paintBorder);
        paintBorder.setShadowLayer(4.0f, 1.0f, 1.0f, Color.BLACK);

        hexagonPath = new Path();
        hexagonBorderPath = new Path();
    }
    
    public void setRadius(float r) {
        this.radius = r;
        calculatePath();
    }
    
    public void setBorderWidth(int borderWidth)  {
        this.borderWidth = borderWidth;
        this.invalidate();
    }
    
    public void setBorderColor(int borderColor)  {
        if (paintBorder != null)
            paintBorder.setColor(borderColor);

        this.invalidate();
    }
    
    private void calculatePath() {
        
        float triangleHeight = (float) (Math.sqrt(3) * radius / 2);
        float centerX = viewWidth/2;
        float centerY = viewHeight/2;
        
        hexagonBorderPath.moveTo(centerX, centerY + radius);
        hexagonBorderPath.lineTo(centerX - triangleHeight, centerY + radius/2);
        hexagonBorderPath.lineTo(centerX - triangleHeight, centerY - radius/2);
        hexagonBorderPath.lineTo(centerX, centerY - radius);
        hexagonBorderPath.lineTo(centerX + triangleHeight, centerY - radius/2);
        hexagonBorderPath.lineTo(centerX + triangleHeight, centerY + radius/2);
        hexagonBorderPath.moveTo(centerX, centerY + radius);
    
        float radiusBorder = radius - borderWidth;    
        float triangleBorderHeight = (float) (Math.sqrt(3) * radiusBorder / 2);
        
        hexagonPath.moveTo(centerX, centerY + radiusBorder);
        hexagonPath.lineTo(centerX - triangleBorderHeight, centerY + radiusBorder/2);
        hexagonPath.lineTo(centerX - triangleBorderHeight, centerY - radiusBorder/2);
        hexagonPath.lineTo(centerX, centerY - radiusBorder);
        hexagonPath.lineTo(centerX + triangleBorderHeight, centerY - radiusBorder/2);
        hexagonPath.lineTo(centerX + triangleBorderHeight, centerY + radiusBorder/2);
        hexagonPath.moveTo(centerX, centerY + radiusBorder);
        
        invalidate();
    }
    
    private void loadBitmap()  {
        BitmapDrawable bitmapDrawable = (BitmapDrawable) this.getDrawable();

        if (bitmapDrawable != null)
            image = bitmapDrawable.getBitmap();
    }
    
    @SuppressLint("DrawAllocation")
    @Override
    public void onDraw(Canvas canvas){
        super.onDraw(canvas);

        loadBitmap();

        // init shader
        if (image != null) {
            
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            
            shader = new BitmapShader(Bitmap.createScaledBitmap(image, canvas.getWidth(), canvas.getHeight(), false), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
            paint.setShader(shader);
            
            canvas.drawPath(hexagonBorderPath, paintBorder);
            canvas.drawPath(hexagonPath, paint);
        }

    }
    
    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        
        int width = measureWidth(widthMeasureSpec);
        int height = measureHeight(heightMeasureSpec, widthMeasureSpec);

        viewWidth = width - (borderWidth * 2);
        viewHeight = height - (borderWidth * 2);

        radius = height / 2 - borderWidth;

        calculatePath();
        
        setMeasuredDimension(width, height);
    }
    
    private int measureWidth(int measureSpec)   {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY)  {
            result = specSize;
        }
        else {
            result = viewWidth;
        }

        return result;
    }

    private int measureHeight(int measureSpecHeight, int measureSpecWidth)  {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpecHeight);
        int specSize = MeasureSpec.getSize(measureSpecHeight);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        }
        else {
            result = viewHeight;
        }

        return (result + 2);
    }
    
    
}

komment

Rajawali vs libgdx összehasonlítás, libgdx tömörítés

2014. március 13. 08:31 - lacas8282

ok kezdjük...

Kezdetben használtam az én saját magam által írt 3d engine-emet androidon. A hátulütői, hogy opengl es 1.x-et tudott csak, nem voltak shaderek, így egy csomó dolgot nem lehetett szépen megvalósítani. Plusz minden fixed function pipeline volt, Tehát nagyon egyszerű. Nem votlak tényleges Matrix4-ek, meg Vector-ok se nagyon, amivel bíbelődni kellett. Nem voltak lighting shaderek, csak sima osztályok. Nem volt custom árnyékolás, árnyékok, etc.

Ezután felfedeztem a Rajawali szépségeit. Végre volt shader, meg szép volt meg minden. Aztán azt is felfedeztem, hogy dög lassú. Nem java/android alapra tervezték a grafikus cuccok írását. És ráadásul nem hiszem, hogy az egész engine jól lenne megírva. Hatalmas hibák vannak benne, mint pl.: memória sokat zabál, még akkor is ha előtte ETC1-be konvertáltunk dolgokat.

Egy összehasonlítás: egy Rajawali opengl app 40-50 megát evett, míg a hasonszőrú libgdx társa 15-18 MB-ot.

Nézzük a betöltési időket:

Nyilván nagy képméretekkel még a libgdx-nél is sokat fog tölteni, hiába van megírva c++-ban és hiába jni-zünk.

Nézzük tehát a libgdx-et:

15 db!!! 2048-as png képet töltöttem be a Galaxy Note2-esemen. libgdx-nél ez már kicsit lassúnak tűnik, azért mégis tömörítetlen png-kről volt szó. 9 mperc idő telt el mire betöltötte, a 2048-as textúrák kirajzolása sem ment zökkenőmentesen, 20-30 FPS volt, és szakadozott... Nyilván mobilon hülyeség 2048as képeket is használni, de nekem direkt ennyi kellett. Gondoltam ez egy kissé durva, ezért konvertáltam a képeket először jpeg-be, majd ETC1-be. A libgdx-nek saját ETC1 compressor-ja van, ami kódból működik, így ne is próbáljuk meg használni a Kronos féle (?) convertert. Nem fog működni vele...

A konvertálás androidon, kódból kissé lassú, dehát 15 db 2048-as textúráról van szó. Szóval kb 50 mpercet vett el az életemből. Ezután a libgdx a SD_card/data/projectnev könyvtárba menti el az ETC1 file-okat.

A 15 db ETC1-em filemérete kb 1/6-a lett az eredeti fileoknak. Ez már önmagában jó!

Ok, nézzük az ETC1-ekkel a betöltést... 15 db 2048-as ETC1 betöltés, kb 900 ms a Note2-mön. Renderelés: stabil 60 FPS. Valószínűleg a grafika azért nem olyan szép a tömörítés miatt, de emberi szemmel elfogadható. Az ETC1 alpha csatornát nem kezel, így erről ne is álmodjunk. Valamint png-t on the fly nem fog tömöríteni a libgdx, jpeg-re van kitalálva...

obj file-ok betöltés...Ez megint csak lassú, főleg nagy számú triangle-kkel. Ezért ezeket konvertálnunk kell fbx-conv exe-vel (64 bites rendszernél a 64 bites mappa exe-jét használjuk)

Ez kb így néz ki: fbx-conv -f myfile.obj

A -f kapcsolóra szükségünk lesz, mert ez a textúrát flippeli, így lesz rajta a textúránk jól az object-en.
Ezután ebből készül egy g3db file, ami egy bináris optimalizált file, gyorsabban töltődik be. Elméletileg, gyakorlatilag ha nagyon sok polygonunk van, akkor nagyon gyors ez sem lesz, akkor valamilyen módon Async-ben kell betölteni a cuccainkat.

Ha egy textúrát át akarunk rajzolni/update-elni ezt is megtehetjük akár real time, de ekkor a gdx thread-jén kell dolgoznunk, azaz egy Runnable-ön elküldeni neki a dolgokat. UI Threadről nem fog menni a GL Threades dolog...

Async betöltésre elméletileg alkalmas az AssetLoader, de nekem még nem igazán működik.

Végezetül, én sem ma kezdtem a libgdx-el való ismerkedést, a kezdetek nagyon nehezek, az első pár hét/hó, de ha van egy kis shader tudásunk, illetve matek akkor nyert ügyünk van.

Igazándiból a libgdx erősen rámegy a matekra, így anélkül nem is lehet megfogni a dolgot.

 

komment
Címkék: start libgdx
süti beállítások módosítása