Страницы

Поиск по вопросам

Показаны сообщения с ярлыком canvas. Показать все сообщения
Показаны сообщения с ярлыком canvas. Показать все сообщения

понедельник, 13 апреля 2020 г.

Движение объекта (HTML5 canvas)

#canvas #javascript #html5 #алгоритм

                    
Привет всем, нужна реализация следующего: анимация по рандомным координатам ходит
в произвольные стороны в рамках разрешения пользователя. Каков алгоритм сего действия?    


Ответы

Ответ 1



Генерируешь рандомный X Проверяешь, не вылазит ли он за пределы экрана Генерируешь рандомный Y Проверяешь, не вылазит ли он за пределы экрана Указываешь координаты твоему объекту Делаешь паузу Повторяешь 1-6 пункт

Перевод функции arcTo -> bezier

#canvas #алгоритм

                    
Как бы превратить arcTo (стандартная функция postScript, да и не только, например
в HTML5Canvas она есть) с параметрами x1, y1, x2, y2, radius в кривую Безье?    


Ответы

Ответ 1



Вспомнить тригонометрию : ) Бился с аналогичным вопросом в Processing. Вопрос популярный и решения гуглятся. Например, даже с интерактивным примером.

Ответ 2



Занимался аналогичным вопросом. Вот в помощь картинка в svg для вычисления расстояния (l) до контрольной точки, тоже не для эллипса к сожалению, но думаю можно как-нибудь обобщить.

Как оптимизировать код?

#android #canvas

                    
Нужно сделать так, чтобы за пальцем двигалась картинка (Bitmap f), а  и выполнялось
"стирание",
но алгоритм движения картинки очень сильно нагружает устройство, и в итоге FPS=8.
Без движения картинки "стирание" происходит при довольно большой частоте кадров. Как
можно ускорить/оптимизировать этот код, чтобы картинка двигалась без лагов? Заранее
ОГРОМНОЕ спасибо!
public class WScratchView extends SurfaceView implements SurfaceHolder.Callback
{
    private Context mContext;
    public WScratchViewThread mThread;
    List mPathList = new ArrayList();
    private int mOverlayColor;
    private Paint mOverlayPaint;
    private int mRevealSize;
    private boolean mIsScratchable = true;
    private boolean mIsAntiAlias = false;
    private Path path;
    private float startX = 0;
    private float startY = 0;
    private boolean mScratchStart = false;
    private MediaPlayer phrase;
    Boolean timerRunned = false;
    int fWidth,fHeight, fX,fY, fIndent;
    boolean ignore_drawable = false;
    Bitmap bitmap, f;
    public static long scores = 0;
    public Paint paint, paintf;
    public Rect destin, destinf;
    public int fLeft, fTop, fRight, fBottom;
    Handler h;
    Runnable updateScores;

    public WScratchView(Context ctx, AttributeSet attrs)
    {
        super(ctx, attrs);
        init(ctx, attrs);
    }

    public WScratchView(Context context)
    {
        super(context);
        init(context, null);
        }

    private void init(Context context, AttributeSet attrs)
    {
        mContext = context;

        mOverlayColor = DEFAULT_COLOR;
        mRevealSize = DEFAULT_REVEAL_SIZE;

        setZOrderOnTop(true);   
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
        holder.setFormat(PixelFormat.TRANSPARENT);

        mOverlayPaint = new Paint();   
        mOverlayPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR)); 
        mOverlayPaint.setStyle(Paint.Style.STROKE);
        mOverlayPaint.setStrokeCap(Paint.Cap.ROUND);
        mOverlayPaint.setStrokeJoin(Paint.Join.ROUND);

        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.layer);
        f = BitmapFactory.decodeResource(getResources(), R.drawable.f);

        Resources res = getResources();
        fHeight = res.getInteger(R.integer.fHeight);
        fWidth  = res.getInteger(R.integer.fWidth);
        fIndent = res.getInteger(R.integer.fIndent);

        h = new Handler();
        updateScores = new Runnable() {

            @Override
            public void run()
            {
                Main.tvScores.setText(Long.toString(scores));                   
            }
        };

        paint = new Paint();
        paintf = new Paint();
        paint.setFilterBitmap(true);
        paintf.setFilterBitmap(false);

        Log.d("LOG_", "init complete");
    }

    @SuppressLint("DrawAllocation") @Override
    public void onDraw(Canvas canvas)
    { 
        if (ignore_drawable)return;
        destin = new Rect(0, 0, getWidth(), getHeight());
        canvas.drawBitmap(bitmap, null, destin, paint);

        //Log.d("LOG_","draw start");
        for (Path path: mPathList)
        {
            mOverlayPaint.setAntiAlias(mIsAntiAlias);
            mOverlayPaint.setStrokeWidth(mRevealSize);

            canvas.drawPath(path, mOverlayPaint);
        }

        destinf = new Rect(fLeft, fTop, fRight, fBottom);
        canvas.drawBitmap(f, null, destinf, paintf);
    }

    @Override
    public boolean onTouchEvent(MotionEvent me)
    {        
        ignore_drawable = true;
        synchronized (mThread.getSurfaceHolder())
        {
            ignore_drawable = false;    
            if (!mIsScratchable)
            {
                return true;
            }

            if (!timerRunned)
            {
                timerRunned = true;
                Main.cdt.start();
            }
            Log.d("LOG_", "bye");

            switch (me.getAction())
            {
                case MotionEvent.ACTION_DOWN:
                    path = new Path();
                    path.moveTo(me.getX(), me.getY());
                    startX = me.getX();
                    startY = me.getY();
                    mPathList.add(path);
                    break;
                case MotionEvent.ACTION_MOVE:
                    playrandom();
                    fX = (int)me.getRawX();
                    fY = (int)me.getRawY();
                    fLeft = fX - fIndent;
                    fTop = fY - fIndent - fIndent;
                    if (fLeft < 0)fLeft = 0;
                    if (fTop < 0) fTop =  0;
                    fRight = fX - fIndent + fWidth;
                    fBottom = fY - (fIndent*2) + fHeight;
                    if (mScratchStart)
                    {
                        path.lineTo(me.getX(), me.getY());
                    }
                    else
                    {
                        if (isScratch(startX, me.getX(), startY, me.getY()))
                        {
                            mScratchStart = true;
                            path.lineTo(me.getX(), me.getY());
                        }
                    }
                    scores += Main.level;   
                    h.post(updateScores);
                    invalidate();
                    break;
                case MotionEvent.ACTION_UP:
                    mScratchStart = false;
                    try
                    {phrase.stop();}
                    catch (Exception ex) {}
                    break;
            }
            invalidate();
            return true;
        }
    }

    public void playrandom()
    {
        //Тут идёт очень большой метод, не связанный с вопросом. Я его пропущу.
    }

    private boolean isScratch(float oldX, float x, float oldY, float y)
    {
        float distance = (float) Math.sqrt(Math.pow(oldX - x, 2) + Math.pow(oldY
- y, 2));
        if (distance > mRevealSize * 2)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {}

    @Override
    public void surfaceCreated(SurfaceHolder arg0)
    {
        mThread = new WScratchViewThread(getHolder(), this); 
        mThread.setRunning(true);
        mThread.setPriority(1);
        mThread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder arg0)
    {
        boolean retry = true;
        mThread.setRunning(false);
        while (retry)
        {
            try
            {               
                mThread.join();

                retry = false;
            }
            catch (InterruptedException e) {}
        }

    }

    class WScratchViewThread extends Thread
    {
        private SurfaceHolder mSurfaceHolder;
        private WScratchView mView;
        private boolean mRun = false;

        public WScratchViewThread(SurfaceHolder surfaceHolder, WScratchView view)
        {
            mSurfaceHolder = surfaceHolder;
            mView = view;
        }

        public void setRunning(boolean run)
        {
            mRun = run;
        }

        public SurfaceHolder getSurfaceHolder()
        {           
            return mSurfaceHolder;
        }

        @SuppressLint("WrongCall") @Override
        public void run()
        {           
            Canvas c;
            while (mRun)
            {
                c = null;
                try
                {
                    c = mSurfaceHolder.lockCanvas(null);
                    if (c != null)
                        synchronized (mSurfaceHolder)
                        {
                            {   
                                mView.onDraw(c);
                            }
                        }
                }
                finally
                {
                    if (c != null)
                    {
                        mSurfaceHolder.unlockCanvasAndPost(c);
                    }
                }

                try
                {
                    TimeUnit.MILLISECONDS.sleep(50);
                }
                catch (InterruptedException e) {}
            }
        }
    }

    public void resetView()
    {
        synchronized (mThread.getSurfaceHolder())
        {
            mPathList.clear();
        }
    }

    public boolean isScratchable()
    {
        return mIsScratchable;
    }

    public void setScratchable(boolean flag)
    {
        mIsScratchable = flag;
    }

    public void setOverlayColor(int ResId)
    {
        mOverlayColor = ResId;
    }

    public void setRevealSize(int size)
    {
        mRevealSize = size;
    }

    public void setAntiAlias(boolean flag)
    {
        mIsAntiAlias = flag;
    }
}
    


Ответы

Ответ 1



Только по коду сказать что-то конкретное сложно. Стоит прогнать ваше приложение через профайлер (насколько я помню, он входит в состав Android SDK) и проверить, какой метод отъедает больше всего времени. Точно не скажу, но мне кажется, что это метод onTouchEvent. Во-превых, лучше не использовать блок synchronized, такая синхронизация отъедает слишком много ресурсов. Вместо него лучше использовать локи (также почитайте про методы lockCanvas() и unlockCanvasAndPost()). Во-вторых, этот метод будет отрабатывать очень много раз в секунду на каждое касание, что скорее всего и нагружает процессор. Для того, чтобы этого избежать стоит ограничить число срабатываний этого метода в секунду. Для этого подойдёт проверка на время предыдкщего срабатывания метода: если он отработал меньше чем, скажем, 50 милисекунд назад, то не вызываем обработчик.

суббота, 11 апреля 2020 г.

Изменение слоя в jCanvas

#javascript #jquery #canvas

                    
Рисую, используя jCanvas. Создала текст, который меняет цвет при наведении мыши.
Но я использую setLayer, то есть, получается, создаю новый слой. Поэтому новая надпись
исчезает при наведении мыши. Непонятно только почему на весь слой, а не только на первый
текст.

Мне нужно, чтобы первый текст по-прежнему менял цвет при наведении мыши, но все остальное
оставалось неизменным.

Демо.

$('canvas').drawText({
    fillStyle: 'green',
    strokeWidth: 2,
    x: 150, y: 100,
    fontSize: 48,
    fontFamily: 'Verdana, sans-serif',
    text: 'Hello',
    layer: true,
    mouseover: function(layer) {
        $(this).setLayer(layer, {
            fillStyle:'red',
        })
        .drawLayers();
    },
    mouseout: function(layer) {
        $(this).setLayer(layer, {
            fillStyle:'green'
        })
        .drawLayers();
    },
 });

$('canvas').drawText({
    x: 150, y: 300,
    fillStyle: 'green',
    strokeWidth: 2,
    fontSize: 34,
    text: 'Hello'
})

    


Ответы

Ответ 1



Проблема заключается в том, что у второго текста не установлено свойство layer: true. Как результат - его нет в списке слоёв, и при вызове .drawLayers(); он перестаёт быть видимым. Исправленный код: var $canvas = $('canvas'); $canvas.drawText({ fillStyle: 'green', strokeWidth: 2, x: 150, y: 100, fontSize: 48, fontFamily: 'Verdana, sans-serif', text: 'Hello', layer: true, mouseover: function(layer) { $(this).setLayer(layer, { fillStyle: 'red' }).drawLayers(); }, mouseout: function(layer) { $(this).setLayer(layer, { fillStyle: 'green' }).drawLayers(); } }); $canvas.drawText({ x: 150, y: 300, fillStyle: 'green', strokeWidth: 2, fontSize: 34, text: 'Hello', layer: true }); Полный пример в jCanvas Sandbox.

Проблема с сохранением canvas

#php #javascript #jquery #canvas

                    
Есть div, из содержимого этого div формируется canvas,потом я пытаюсь сохранить это в png

$("#s_img1").click(function (){

 html2canvas($("#tableWrap"), {
            onrendered: function(canvas) {

                theCanvas = canvas;
                document.body.appendChild(canvas);


                $("canvas").attr("id", "canv");
                $("#img-out").append(canvas);

            }
        });
    var data = document.getElementById("canv").toDataURL();

    $.post("process.php", {
imageData : data
}, function(data) {
window.location = data;
  });

   var myCanvas = $(document).find('#canv');
  var myImg = myCanvas.get(0).toDataURL();



alert(myImg);

});


Проблема в том,что консоль дает ошибку на toDataURL();

Uncaught TypeError: Cannot read property 'toDataURL' of undefined


Подскажите в чем проблема? 
Вот process.php

$data = substr($_POST['imageData'], strpos($_POST['imageData'], ",") + 1);
$decodedData = base64_decode($data);
$fp = fopen("canvas.png", 'wb');
fwrite($fp, $decodedData);
fclose();
echo "/canvas.png";


Тут html






Right click to save me!


Ответы

Ответ 1



Проблема том, что при вызове var myCanvas = $(document).find('#canv'); элемент canv еще не создано. Так будет работать: function CallPost(postdatа) { $.post("process.php", {imageData: postdatа}) .done(function (data) { window.location = 'canvas.png'; }); } $(document).ready(function() { $("#s_img1").click(function () { html2canvas($("#tableWrap"), { onrendered: function (canvas) { CallPost(canvas.toDataURL()); } }); }); });

воскресенье, 29 марта 2020 г.

transition в canvas

#javascript #анимация #canvas #animate


Можно ли элементу в canvas добавить transition во время анимации? 






    
    Speedometer
    
    


Время загрузки 0.1 сек


Ответы

Ответ 1



CSS transition никак не применить к элементам на канве. Для получения плавной анимации в общем случае необходимо получать значения в зависимости от времени а не прибавлять каждый кадр фиксированные значения: Я изменил в Вашем примере одну функцию: let delay = 2; // задержка перед анимацией let duration = 3; // кол во секунд анимации роста значения let time = new Date().getTime(); // Время начала анимации let Engine = () => { // кол-во секунд с начала анимации let t = (new Date().getTime() - time) / 1000; val(+getValueFromTime(t).toFixed(1)); requestAnimFrame(Engine); }; // получить значение спидометра в зависимости от текущего времени function getValueFromTime(t) { if (t < delay) // если еще не прошло время задержки return 0 // вернем 0 t -= delay; // иначе вычитаем время задержки if (t < 0.95*duration) { // если прошло меньше 95 % времени анимации // переводим значение времени в значение при помощи функции сглаживания // EasingFunctions.easeOutCubic (значение на входе должно быть от 0 до 1) return 98 * EasingFunctions.easeOutCubic(t/duration); } // иначе это функция от синуса от этого значения return 98 + Math.sin(t * 55); } Функции сглаживания взяты отсюда, больше функций сглаживания тут
Время загрузки 0.1 сек


четверг, 19 марта 2020 г.

Прямоугольная трапеция div

#css3 #canvas #html #веб_дизайн #html5


Как сделать прямоугольную трапецию из одного div. Не из двух (треугольник + прямоугольник),
а сразу с одного. Можно сделать трапецию равнобедренную, так не сойдет. Нужно именно
Пример. Потому что внутри я хочу заполнить текстом.    


Ответы

Ответ 1



#trapezoid { border-top: 100px solid red; border-left: 50px solid transparent; height: 0; width: 100px; } #trapezoid span { margin-top: -100px; position: absolute; }
Текст


воскресенье, 15 марта 2020 г.

Как отрисовать часть Битмапа посредством метода DrawBitmap(Canvas)

#android #canvas #bitmap #border #drawbitmap


Всем привет,имеется следующее:
 :  


FrameLayout(отображен красным цветом)  
Исходный ImageView(черный)  
Квадричный объект(imageview) с прикрученным OnTouchListener'ом (оранжевый),он создан
внутри FrameLayout'a.


Посредством Объекта с OnTouchListener"ом,я хочу показывать "часть" Битмапа,который
установлен в исходный ImageView.

В общем я делаю что то подобное:
Bitmap bt = Bitmap.createBitmap(sourceBitmap,event.getX(),event.getY(),250,250);   

где:   


SourceBitmap - изображение,которые установлено в исходный ImageView  
event.getX() / event.getY()  координаты(квадричного объекта),с которого я начинаю
рисовать "часть" Битмапа.
250,250 - размеры "кусочка" Битмапа.


и полученный результат :  



В общем проблема появляется,когда объект(с прикрученным OnTouchListener'ом),переходит
за границу исходного ImageView(имеется такая возможность,т.е. половина длины,может
уйти за границу).

Т.е. в этом случае :

я ожидаю этот результат:

конкретнее :  


Часть битмапа.  
Вторая часть,это "пустота",то бишь цвет Framelayout(background).


Что я пытался на текущий момент:  

public boolean onTouch(View view, MotionEvent event) {

    switch (event.getAction()) {
        case MotionEvent.ACTION_MOVE:

            //я хочу получить большую порцию,чем текущие координаты
            int CurrentX = (int)view.getX() - (view.getWidth());
            int CurrentY = (int)view.getY() - (view.getHeight());

            //случай когда изображение ушло за границы
            if(CurrentX <= 0)
            {
                Paint paint = new Paint();
                paint.setStyle( Style.FILL  );
                paint.setColor( Color.RED );

                mBitmap = Bitmap.CreateBitmap(sourceBitmap,(int)view.getX() + Math.abs(CurrentX),(int)view.getY(),250,250);
                Canvas canvas = new Canvas(mBitmap);
                canvas.drawBitmap(mBitmap,new Rect((int)view.getX()+Math.abs(CurrentX),
(int)view.getY(),250-Math.abs(CurrentX),250),new RectF(Math.abs(CurrentX), 0, 250,250),paint);
            }
            break;
    }

    return true;
}
}


И проблема заключается в том,что результат не тот,который я ожидаю.
А так же,как можно получить "результат"(прямоугольник),без исходного изображения,т.к.
из за canvas.drawBitmap результат рисуется поверх оригинального изображения.
    


Ответы

Ответ 1



В моем случае, а именно,когда объект с прикрученным OnTouchListener, который может уходить за границы X,Y осей,относительно исходного ImageView,я сделал специальные правила(пост условия). Условия: Width = Ширина ImageView,в котором я буду показывать результат. Height = Высота ImageView,в котором я буду показывать результат. Левая cторона: X_Coord < 0 && Y_Coord - Height / 2 < 0 && Y_Coord < Bitmap.Height - это будет верхняя часть. X_Coord < 0 && Y_Coord - Height / 2 > 0 && Y_Coord < Bitmap.Height - это будет средняя часть. X_Coord < 0 && Y_Coord - Height / 2 > 0 && Y_Coord > Bitmap.Height - это будет нижняя часть. Правая сторона: X_Coord > Bitmap.Height && Y_Coord - Height / 2 > 0 && Y_Coord < Bitmap.Height - это будет средняя часть. X_Coord > Bitmap.Height && Y_Coord - Height / 2 < 0 && Y_Coord < Bitmap.Height - это будет верхняя часть. X_Coord > Bitmap.Height && Y_Coord - Height / 2 > 0 && Y_Coord > Bitmap.Height - это будет нижняя часть. Стандарт(средняя часть,которая не доходит краев(правой \ левой стороны) исходного ImageView): X_Coord - Width / 2 > 0 && X_Coord < Bitmap.Width && Y_Coord - Height / 2 < 0 && Y_Coord < Bitmap.Height - это будет верхняя часть. X_Coord - Width / 2 > 0 && X_Coord < Bitmap.Width && Y_Coord - Height / 2 > 0 && Y_Coord > Bitmap.Height - это будет нижняя часть. X_Coord - Width / 2 > 0 && X_Coord < Bitmap.Width && Y_Coord - Height / 2 > 0 && Y_Coord < Bitmap.Height - это будет средняя часть. Посредством этих "Пост условий", я буду отрисовывать кусочек(порцию) Битмапа в случае MotionEvent.ACTION_MOVE. Давайте рассмотрим пример: public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: int Width = ResultImgView.getWidth(); int Height = ResultImgView.getHeight(); //paint для красного бэкграунда Paint paint = new Paint(); paint.setStyle( Style.FILL ); paint.setColor( Color.RED ); Bitmap mBitmap = null; Canvas canvas = null; //Наше условие if(view.getX() - Width / 2 >= SourceBitmap.getWidth() && view.getY() - Height / 2 > 0 && view.getY() + Height / 2 < SourceBitmap.getHeight()) { //Отлично! Мы зашли сюда,а это значит,что в данный момент времени, мы находимся в **Правой Стороне** и относимся к "Средней" Площади. //Теперь можно начать рисовать порцию битмапа. //Считаем наш отступ по Х int Difference = (int)((view.getX() - Width / 2 ) - SourceBitmap.getWidth(); //не забываем установить этот "отступ" mBitmap = Bitmap.createBitmap(SourceBitmap, ((int)view.getX() - Width / 2) - Difference, (int)view.getY() - Height / 2, Width,Height); canvas = new Canvas(mBitmap); //рисуем наш бэкграунд в виде прямоугольника canvas.drawRect(0,0,mBitmap.Width,mBitmap.getHeight(),paint); //рисуем порцию битмапа canvas.drawBitmap(mBitmap,new Rect(Difference, 0,mBitmap.getWidth(),mBitmap.getHeight()),new Rect(0,0,mBitmap.getWidth() - Difference,mBitmap.getHeight()),null); //на этом всем! } //проделать такого же плана работу и для других условий. break; } return true; }

воскресенье, 8 марта 2020 г.

Создание анимации через потоки на Android

#java #android #многопоточность #canvas #анимация


Здравствуйте, стоит задача, на языке Java под ОС Android нужно написать задачу: создать
снеговика и сделать так, чтобы его составляющие объекты меняли цвет с разной скоростью.
Смена цветов должна быть реализована в потоке.
У меня получилось написать код, но не понимаю, как именно эта анимация должна в этот
поток помещаться. Вот собственно полный код, написан в Eclipse:

package ru.ucheba;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;

public class Zadanie2 extends Activity {
    int a = 255;
    int r = 100;
    int g = 0;
    int b = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new Panel(this));

    }

    class Panel extends View {
        public Panel(Context context) {
            super(context);
        }

        @Override
        public void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            Paint p1 = new Paint();
            p1.setStyle(Style.FILL);

            p1.setARGB(a, r, g, b);
            canvas.drawCircle(270, 170, 70, p1);
            for (int i = 0; i < 5; i++) {
                r--;
                SystemClock.sleep(10);
                invalidate();
            }

            for (int j = 0; j < 15; j++) {
                p1.setARGB(a, r, g, b);
                g--;
                canvas.drawCircle(270, 310, 100, p1);
                SystemClock.sleep(20);
                invalidate();
            }

            for (int k = 0; k < 30; k++) {
                p1.setARGB(a, r, g, b);
                b--;
                canvas.drawCircle(270, 510, 150, p1);
                SystemClock.sleep(40);
                invalidate();
            }
        }

        class Task extends Thread {
            @Override
            public void run() {

            }
        }
    }
}


Чем заполнять метод run() понимаю, но ругается либо на canvas, p1, или invalidate().
Прошу помощи
    


Ответы

Ответ 1



Ваш код принципиально неверен. В методе onDraw() нельзя вызывать sleep() и invalidate(). Это все вместе с изменением переменных argb должно происходить в отдельном потоке, а метод onDraw() только отрисовывать экран в соответствии с их состоянием. @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); p.setARGB(a, r1, g1, b1); canvas.drawCircle(270, 170, 70, p); p.setARGB(a, r2, g2, b2); canvas.drawCircle(270, 310, 100, p); p.setARGB(a, r3, g3, b3); canvas.drawCircle(270, 510, 150, p); } Перенести в конструктор и сделать переменной класса: p = new Paint(); p.setStyle(Style.FILL); В run() меняйте rX, gX, bX как вам надо и вызывайте синхронно с основным потоком invalidate() когда надо перерисовать.

Ответ 2



Выполнил вот таким образом, может кому-то поможет. Думаю, этот код можно написать гораздо лучше. Поэтому кто сможет, укажите на ошибки, буду благодарен. import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Bundle; import android.view.View; public class Zadanie2 extends Activity { int[] Colo = { 10, 20, 30 }; // массив со значениями исп. для цветов // снеговика public Thread myThread1, myThread2, myThread3; // создание переменных для потоков @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new Panel(this)); // использование класса Panel в качестве активити myThread1 = new Thread(new Runnable() { @Override public void run() { int znak = 1; while (true) { if ((znak > 0) && (Colo[0] > 250)) { znak = -znak; } if ((znak < 0) && (Colo[0] < 5)) { znak = -znak; } Colo[0] += znak; try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } } } }); myThread2 = new Thread(new Runnable() { @Override public void run() { int znak2 = 1; while (true) { if ((znak2 > 0) && (Colo[1] > 250)) { znak2 = -znak2; } if ((znak2 < 0) && (Colo[1] < 5)) { znak2 = -znak2; } Colo[1] += znak2; try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } } } }); myThread3 = new Thread(new Runnable() { @Override public void run() { int znak3 = 1; while (true) { if ((znak3 > 0) && (Colo[2] > 250)) { znak3 = -znak3; } if ((znak3 < 0) && (Colo[2] < 5)) { znak3 = -znak3; } Colo[2] += znak3; try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } } } }); // запуск потоков myThread1.start(); myThread2.start(); myThread3.start(); } class Panel extends View { public Panel(Context context) { super(context); } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); float w, h, cx, cy, radius; // переменные, для адаптивного расположения снеговика w = getWidth(); // считывает ширину h = getHeight(); // считывает высоту cx = w / 2; cy = h / 2; // для ориентации экрана if (w > h) { radius = h / 8; } else { radius = w / 8; } Paint p1 = new Paint(); p1.setStyle(Paint.Style.FILL); p1.setColor(Color.rgb(Colo[0], 255, 255)); canvas.drawCircle(cx, cy - h / 3, radius, p1); Paint p2 = new Paint(); p2.setStyle(Paint.Style.FILL); p2.setColor(Color.rgb(255, Colo[1] * 2, 255)); canvas.drawCircle(cx, cy - h / 3 + radius * 2, (float) (radius * 1.5), p2); Paint p3 = new Paint(); p3.setStyle(Paint.Style.FILL); p3.setColor(Color.rgb(255, 255, Colo[2] * 3)); canvas.drawCircle(cx, cy - h / 3 + radius * 5, radius * 2, p3); invalidate(); // перерисовка объектов } } }

четверг, 5 марта 2020 г.

Синхронизировать канвас с анимацией

#javascript #css3 #анимация #canvas #css_animation


В каждом фрейме отрисовки (requestAnimationFrame) в центре канваса рисуется круг.
Размер канваса анимируется. В Хроме и FF круг немного дрожит, а в IE он заметно толстеет
перед перерисовкой - т. е. браузер сначала растягивает имеющуюся картинку, а потом
заменяет её новой. Почему так происходит и как исправить?



Насколько я представляю, если я делаю что-то в requestAnimationFrame, то содержимое
страницы вообще не должно перерисовываться, пока я не закончу. Соответственно, на момент
отрисовки должна отрисоваться сразу новая версия. Но в IE сценарий не такой?

https://jsfiddle.net/3wLroppt/



function draw() {
  var canvas = document.querySelector('canvas');
  
  var width = canvas.clientWidth, height = canvas.clientHeight;
  
  canvas.width = width * 2;
  canvas.height = height * 2;
  
  var ctx = canvas.getContext('2d');
  
  var cx = width, cy = height, r = height;
  
  ctx.beginPath();
  ctx.arc(cx, cy, r, 0, 2 * Math.PI, false);
  ctx.fill();
}

requestAnimationFrame(function frame() {
  draw();
  requestAnimationFrame(frame);
});
canvas {
  outline: 1px dotted red;
  height: 200px;
  width: 200px;
  display: block;
  margin: auto;
  animation: w-100-200 .5s linear infinite;
}

@keyframes w-100-200 {
    0% { width: 200px; }
   50% { width: 400px; }
  100% { width: 200px; }
}




    


Ответы

Ответ 1



Нашел ответ, правда на IE пока не тестировал, у меня linux))) Как я и подозревал дело было в том что анимация с высотой и шириной по умолчанию происходит так: Например у вас ширина должна с 200px расшириться до 400px в определенном промежутке времени ($t), и расчет происходит так - к ширине добавляется один пиксель в (400-200) / $t времени.И кода размер ширины контейнера нечетное количество пикселей то центр круга рисуется на один пиксель вправо (по оси x) (201/2 пиксель шириной для него будет 101px), и это приводит к непредусмотренной анимации (дрожанию) круга внутри контейнера. Эту проблему я решил свойством animation-timing-function, у него есть значение steps. там можно задать шаг анимации и у нас поскольку от 200 - 400 будет 100 2-пиксельных шагов прописываем в нем 100 animation-timing-function: steps(100); И вот что у меня получилось: А это пример от автора но время я поставил 5s что бы наглядно было видно непредусмотренная анимация черного круга.

Ответ 2



Вы же сами написали - браузер сначала рисует кружок с помощью js,а css его тем временем растягивает. let start = Date.now(); const canvas = document.querySelector('canvas'); const ctx = canvas.getContext('2d'); const height = canvas.clientHeight; function draw() { let dt = Date.now() - start; var side = Math.pow(-1, ~~(dt/500)); var delta_width = 200 * (dt%500)/500; width = canvas.width = ((side>0)?200:400) + side * delta_width; prev = Date.now(); var cx = width/2, cy = height, r = height; ctx.beginPath(); ctx.arc(cx, 100, 100, 0, 2 * Math.PI, false); ctx.fill(); } setInterval(draw, 54); canvas { outline: 1px dotted red; height: 200px; display: block; margin: auto; }

воскресенье, 1 марта 2020 г.

Как нарисовать треугольник по трём сторонам?

#javascript #canvas


У меня будет 3 интупа и баттон. При нажатии на этот батон должен нарисоваться треугольник
у которого 3 стороны равны значениям в инпутах. Как это организовать? Нагуглил 

context.beginPath();
            context.moveTo(30, 20);
            context.lineTo(some_value);
            context.lineTo(some_value);
            context.closePath();


Но этот способ подходит для рисования, по двум сторонам.
    


Ответы

Ответ 1



Вам надо найти координаты вершин и соединить их отрезками. Пусть одна сторона (например, A) лежит вдоль оси 0X. Тогда её Y-координата будет равна 0. А значит и двух вершин треугольника тоже. Остаётся найти ещё одну вершину и соединить её с концами этого отрезка A. Я бы для начала нашёл углы треугольника. Это можно сделать из двух разных формул поиска площади треугольника: по формуле Герона и зная, что площадь равна произведению длин двух сторон на синус угла между ними. После этого можно опустить высоту из вершины, координаты которой ищем, и внутри прямоугольного треугольника легко найдём x- и y-компоненты координаты. btn.onclick = () => { let A = 1 * a.value, B = 1 * b.value, C = 1 * c.value; let p = (A + B + C) / 2; let S = Math.sqrt(p * (p - A) * (p - B) * (p - C)); let SinA = 2 * S / (A * B); let alpha = Math.asin(SinA); let x = B * Math.cos(alpha), y = B * Math.sin(alpha); let ctx = img.getContext("2d"); ctx.beginPath(); ctx.moveTo(10 + A, 70); ctx.lineTo(10, 70); ctx.lineTo(10 + x, 70 - y); ctx.lineTo(10 + A, 70); ctx.stroke(); }



Матчасть: Как найти углы треугольника по трем его сторонам

Ответ 2



Вам нужно будет сначала вычислить координаты каждой точки. Вы можете установить первую точку на [0, 0], а вторая будет на [x1, 0], где x1 - длина первой строки. Третью точку можно рассчитать так: var AB = 40; var BC = 50; var AC = 30; var A = [0, 0]; // начальные координаты var B = [0, AB]; var C = []; // вычисление третьей точки C[1] = (AB * AB + AC * AC - BC * BC) / (2 * AB); C[0] = Math.sqrt(AC * AC - C[1] * C[1]); var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); ctx.beginPath(); ctx.moveTo(A[0], A[1]); ctx.lineTo(B[0], B[1]); ctx.lineTo(C[0], C[1]); ctx.fill();

пятница, 28 февраля 2020 г.

Как изменить цвет, размер сектора круга при наведении на него?

#javascript #html5 #canvas


Есть статичная круговая диаграмма. 

Задача при наведении на сектор, 


вызвать callback функцию
поменять размер сектора
цвет
а еще если можно выполнить дополнительную обводку по периметру окружности  пример
на картинке


но последний пункт, если это слишком трудно, и костылем решить смогу (если получится
справиться с первыми двумя пунктами), наложив одну диаграмму на другую.

Html структуру менять можно, главное получить результат.





var canvas = document.getElementById("can");
var ctx = canvas.getContext("2d");
var lastend = 0;
var data = [200, 60, 15]; //мои сектора
var myTotal = 0; 
var myColor = ['red', 'green', 'blue']; //цвета

for (var e = 0; e < data.length; e++) {
  myTotal += data[e];
}

for (var i = 0; i < data.length; i++) {
  ctx.fillStyle = myColor[i];
  ctx.beginPath();
  ctx.moveTo(canvas.width / 2, canvas.height / 2);
  // параметры: x, y, радиус, начальный угол, конечный угол, antiClockwise (boolean)
  ctx.arc(canvas.width / 2, canvas.height / 2, canvas.height / 2, lastend, lastend
+ (Math.PI * 2 * (data[i] / myTotal)), false);
  ctx.lineTo(canvas.width / 2, canvas.height / 2);
  ctx.fill();
  lastend += Math.PI * 2 * (data[i] / myTotal);
}




    


Ответы

Ответ 1



Предлагаю такой способ, считаем дистанцию от мышки до центра круга. Если дистанция меньше радиуса - мы навели на какой-то сектор, а на какой конкретно определяем по углу направления в сторону курсора мышки относительно центра круга. let data = [200, 60, 15]; let colors = ['red', 'green', 'blue']; let radius = canvas.height / 2 - 10; let ctx = canvas.getContext("2d"); let total = data.reduce((a, b) => a + b); let radiuses = [radius, radius, radius]; let activeAngle, activeIndex; function callback(active) { stat.textContent = colors[active] || 'none'; canvas.style.cursor = active > -1 ? 'pointer' : 'default'; } function render() { requestAnimationFrame(render); let currentActiveIndex; ctx.clearRect(0, 0, canvas.width, canvas.height); var lastend = 0; for (var i = 0; i < data.length; i++) { ctx.fillStyle = colors[i]; ctx.beginPath(); ctx.moveTo(canvas.width / 2, canvas.height / 2); let endAngle = lastend + (Math.PI * 2 * (data[i] / total)); let isActive = activeAngle && activeAngle > lastend && activeAngle < endAngle; currentActiveIndex = isActive ? i : currentActiveIndex; radiuses[i] += isActive ? 1 : -1; radiuses[i] = Math.min(Math.max(radius, radiuses[i]), radius+10); ctx.arc(canvas.width / 2, canvas.height / 2, radiuses[i], lastend, endAngle); ctx.lineTo(canvas.width / 2, canvas.height / 2); ctx.fill(); lastend += Math.PI * 2 * (data[i] / total); } activeIndex != currentActiveIndex && callback(activeIndex = currentActiveIndex); } requestAnimationFrame(render) canvas.addEventListener('mousemove', e => { let dy = e.layerY - canvas.height / 2; let dx = e.layerX - canvas.width / 2; let d = Math.sqrt(dx*dx + dy*dy) < radius; let a = Math.atan2(dy, dx); activeAngle = d ? a < 0 ? a + 2*Math.PI : a : null; }) none

Ответ 2



Рекомендую использовать для таких целей Pixi или любую другую библиотеку для работы с canvas. Ибо canvas api не имеет поддержки событий наведения или впринципе хранить какие-либо структурированные данные об отрисованных объектах. Для добавления подобных функций нужно писать обертки, которые будут хранить контекст и кастомные характеристики, а судя по твоему коду - тебе не до этого. А вот в Pixi все уже есть, fabric.js тоже отлично подойдет для этой цели. PS на будущее имей в виду, что canvas рендерит только один кадр, если ты видишь взаимодействие с ним и какие-либо анимации (например анимация увеличения размера участка твоего графика), то они реализованы через нативные события и таймеры. на каждую 1/60 (60 кадров в сек.) canvas рендерится заново с новыми входными параметрами для абстрактных объектов.

Ответ 3



class Pipe { constructor(_parent) { this.canvas = Pipe.genCanvasElement(); this.ctx = this.canvas.getContext("2d"); this.curent = null; this.mousemove = Pipe.debounce(this, function (_e) { // console.dir(_e) let xM, yM; xM = _e.offsetX; yM = _e.offsetY; let x, y; x = this.canvas.width / 2; y = this.canvas.height / 2; let radians = Math.atan2(yM - y, xM - x); let degrees = radians * 180 / Math.PI; if ((360 + degrees) < 360) { degrees = 360 + degrees; radians = degrees * Math.PI / 180; } // console.log(`x:${xM} y:${yM} radians:${radians} degrees:${degrees}`) // -- this.curent = radians; // console.log('mousemove') this.render(); }, 500); this.mouseleave = Pipe.debounce(this, function (_e) { this.curent = null; // console.log('mouseleave') if (Pipe.t) clearTimeout(Pipe.t); this.render(); }, 500); this.canvas.addEventListener('mousemove', this.mousemove); this.canvas.addEventListener('mouseleave', this.mouseleave); _parent.appendChild(this.canvas); this.render(); } static debounce(self, f, ms) { return function (...args) { const onComplete = () => { f.apply(self, args); Pipe.t = null; }; if (Pipe.t) clearTimeout(Pipe.t); Pipe.t = setTimeout(onComplete, ms); }; } static genCanvasElement() { let c = document.createElement('canvas'); c.width = 200; c.height = 200; c.style.border = 'solid grey 1px'; return c; } static foo(ctx, fillStyle, x, y, r, startAngle, endAngle) { ctx.beginPath(); ctx.fillStyle = fillStyle; ctx.moveTo(x, y); ctx.arc(x, y, r, startAngle, endAngle, false); ctx.lineTo(x, y); ctx.fill(); ctx.closePath(); } static foo2(startAngle, curent, endAngle, fillStyle, x, y, r, j, ctx) { // console.log(` // startAngle:${startAngle} // endAngle:${endAngle} // this.curent:${this.curent} // `) if ((startAngle < curent) && (curent < endAngle)) { fillStyle = 'aqua'; Pipe.foo(ctx, fillStyle, x, y, r + j, startAngle, endAngle); } else { Pipe.foo(ctx, fillStyle, x, y, r, startAngle, endAngle); } } render() { let ctx = this.ctx; ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); let lastend = 0; let data = [200, 60, 15]; //мои сектора let myColor = ['red', 'green', 'blue']; //цвета let j = this.canvas.width / 8; let x, y, r; x = this.canvas.width / 2; y = this.canvas.height / 2; r = y - j; for (var i = 0; i < data.length; i++) { let startAngle = lastend; let endAngle = lastend + (Math.PI * 2 * (data[i] / 360)); let fillStyle = myColor[i]; Pipe.foo2(startAngle, this.curent, endAngle, fillStyle, x, y, r, j, ctx); // -- lastend += Math.PI * 2 * (data[i] / 360); } if (lastend < Math.PI * 2) { let startAngle = lastend; let endAngle = Math.PI * 2; let fillStyle = 'black'; Pipe.foo2(startAngle, this.curent, endAngle, fillStyle, x, y, r, j, ctx); } } } Pipe.t = null; // --- let pipe = new Pipe(document.body);

среда, 26 февраля 2020 г.

Асинхронное движение изображений в js (canvas)

#javascript #анимация #canvas


Предположим имеется 1 изображение машины. Необходимо создать два объекта Image с
этой машиной и двигать эти два изображения асинхронно. Привожу код, работает он явно
не так как нужно, по задумке - вторая машина должна изначально отставать от первой
и обе должны двигать влево с какой-то скоростью. В итоге получается слияние двух машин,
будто это не два разных объекта, а один.

Наработки: https://jsfiddle.net/o236kta5/2/









    
    






    


Ответы

Ответ 1



Залог плавной анимации - функция положения объектов от времени между кадрами setInterval это плохо, но срабатывает через фиксированные интервалы времени, для вызова отрисовки в современных браузерах есть специальный метод -requestAnimationFrame, браузер сам вызывает функцию переданную в этот метод, когда соберется отрисовать следующий кадр, на разных платформах это работает по-разному, например на мобильных устройствах он вызывается реже для экономии электроэнергии, так же он реже вызывается для неактивных вкладок. let t = 0 let c = canvas.getContext('2d'); let cars = Array(7).fill(0).map((e,i) => ({ x: 560, y: i*20, v: 0.2 + Math.random()/3 })); function draw(dt) { // dt время с начала анимации c.clearRect(0, 0, canvas.width, canvas.height); cars.forEach(car => { car.x -= (dt-t)*car.v; car.x = car.x < 0 ? 600 : car.x; c.drawImage(img, car.x , car.y, img.width, img.height); }); t = dt; requestAnimationFrame(draw); }

Можно ли создать в canvas blur эффект на фото при mousemove?

#javascript #html5 #canvas #blur


Можно ли создать на canvas библиотеке Konva.js (или на каком-то другом) blur эффект
на фото при mousemove?

Читал документацию к Konva js, но ничего подходящего не нашел. Искал примеры, достойных
примеров не нашел.

Вот пример с встроенным фильтром в canvas ctx.filter = 'blur(5px)';.
    


Ответы

Ответ 1



Конечно можно было бы взять библиотеку, но и вручную можно добится неплохих результатов, а заодно и познакомится поближе с обработкой изображений. Это решение использунт прямые манипуляции с пикселями. Применяем ко всем пикселям области, которую необходимо заблюрить, так называемое "ядро свертки" (convolution kernel) - основа основ в обработке изображений. Вот тут подробно про это написано а так же визуализировано Запустите сниппет заново, если картинка не показательная (они псевдослучайные), в сниппете реализовано рисование блюром, как Вы просили в комментариях... let s = 44; // size of area to be blurred let kernel =[[0,0,1,0,0], [0,1,2,1,0], [1,2,3,2,1], [0,1,2,1,0], [0,0,1,0,0]]; let ctx = canvas.getContext('2d'); let total = kernel.flatMap(i => i).reduce((a, i) => a + Math.abs(i)), ks = (kernel.length-1)/2, // half of kernel size - 1 (pixels around center) s2 = s/2, // half size of area to be blurred sks2 = s+ks*2; // size of area of pixels needed to be processed img.onload = e => ctx.drawImage(img, 0, 0); addEventListener('mousedown', e => draw = true) addEventListener('mouseup', e => draw = false) addEventListener('mousemove', e => { if (!window.draw) return; var input = ctx.getImageData(e.layerX-s2-ks, e.layerY-s2-ks, sks2, sks2); var output = ctx.createImageData(s, s); for (var x = 0; x < s; x++) for (var y = 0; y < s; y++) handlePixel(input, output, x, y) ctx.putImageData(output, e.layerX - s2, e.layerY - s2) }) function handlePixel(i, o, x, y) { let offset = (y * s + x) * 4; // offset in output data (4=rgba) for (var kx = -ks; kx <= ks; kx++) for (var ky = -ks; ky <= ks; ky++) { let off = ((y+ks) * sks2 + (x+ks) + kx + ky * sks2) * 4; //offset in input for (var n = 0; n < 3; n++) o.data[offset + n] += kernel[kx+ks][ky+ks] * i.data[off+n] / total; } o.data[offset + 3] = 255; // always opaque } PS: от бага по краям надо подумать как избавиться...

Сохранение отрисованного в Canvas'е в файл

#canvas #java #android #файлы


Здравствуйте. Программирую под систему Android OS. Пишу виджет-класс, наследующий
View. В методе обратного вызова (callback-method) - onDraw(Canvas canvas) происходит
отрисовка графических примитивов и Bitmap'ов по мере взаимодействия пользователя с
данным виджетом. Хотел бы реализовать метод, позволяющий сохранить все, что отрисовывалось
в ходе многократного вызова onDraw на canvas'е, в файл (.jpg, .png). 
Каким образом вообще осуществляется сохранение всего отрисованного в каком-то объекте
(в данном случае Canvas) в файл? Какая цепочка классов вообще нужна для такой операции?
С текстовыми и двоичными файлами были потоки StreamWriter, FileWriter и т.д. Подозреваю
и тут такое потребуется, но как вообще перейти от Canvas'а с его содержимым к конечному
файлу картинки?
И можно ли сохранять файлы изображений в какие-то общедоступные всем каталоги? Скажем,
есть ли в системе Android что-то наподобие "Мои Документы" или "Галерея"?
Заранее спасибо, и извините за слишком примитивные вопросы.    


Ответы

Ответ 1



По поводу вашего вопроса - ответ: StackOverflow И дарю метод сохранения картинки в галерею: private String saveImageToMediaStore(Bitmap source, String title, String desc){ return MediaStore.Images.Media.insertImage(getContentResolver(), source,title, desc); }

четверг, 13 февраля 2020 г.

Проверка на полную прозрачность картинки в Javascript (canvas)

#javascript #canvas


Я пытаюсь реализовать при помощи canvas функционал стирания изображения  ластиком.
Я создаю canvas и задаю ему background в CSS. Поверх canvas я делаю drawImage png
картинки с прозрачностью, к которой я применяю ластик. Мне нужно проверить, стерта
ли картинка полностью, то есть полностью ли прозрачный фон, и осуществить дальше какое-то
действие.

Мой метод создания канваса, отрисовки png на нем и процесса стирания:

var img = new Image;
var canvas = {};

function createCanvas(parent, width, height) {

    canvas.node = document.createElement('canvas');
    canvas.context = canvas.node.getContext('2d');

    img.onload = function(){
    canvas.context.drawImage(img,200,0,img.width*1.2,img.height*1.2); // Or at whatever
offset you like
    };
    img.src = 'raccoon.png';

    canvas.node.width = width || 100;
    canvas.node.height = height || 100;
    parent.appendChild(canvas.node);
    return canvas;
}

function init(container, width, height, fillColor) {
    var canvas = createCanvas(container, width, height);
    var ctx = canvas.context;
    // define a custom fillCircle method
    ctx.fillCircle = function(x, y, radius, fillColor) {
        this.fillStyle = fillColor;
        this.beginPath();
        this.moveTo(x, y);
        this.arc(x, y, radius, 0, Math.PI * 2, false);
        this.fill();
    };
    ctx.clearTo = function(fillColor) {
        ctx.fillStyle = fillColor;
        ctx.fillRect(0, 0, width, height);
    };

    // bind mouse events
    canvas.node.onmousemove = function(e) {
        if (!canvas.isDrawing) {
           return;
        }
        var x = e.pageX - this.offsetLeft;
        var y = e.pageY - this.offsetTop;
        var radius = 80; // or whatever
        var fillColor = '#000000';
        ctx.globalCompositeOperation = 'destination-out';
        ctx.fillCircle(x, y, radius, fillColor);
    };
    canvas.node.onmousedown = function(e) {
        canvas.isDrawing = true;
    };
    canvas.node.onmouseup = function(e) {
        canvas.isDrawing = false;
        if (isFullyTransparent()) {
            redirect();
        };
    };

}

function isFullyTransparent() {
var d = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
for (var i=3; i < d.length; i+=4)
if (d[i]) 
  return false
return true
}

function redirect() {
    };

var container = document.getElementById('canvas');
init(container, 1024, 768, '');




Картинка для примера. Нужен метод, проверяющий, стерт енот полностью или нет.
    


Ответы

Ответ 1



Можно проверить все пиксели на прозрачность при помощи их перебора и поиска не 0 значения: Рисовать левой кнопкой мыши, стирать - правой var img = new Image; var canvas = {}; function createCanvas(parent, width, height) { canvas.node = document.createElement('canvas'); canvas.context = canvas.node.getContext('2d'); img.onload = function() { canvas.context.drawImage(img,0,0,img.width,img.height); canvas.context.globalCompositeOperation = 'destination-out'; }; img.crossOrigin = "anonymous"; img.src = 'https://i.imgur.com/GtBHNPS.png'; canvas.node.width = width || 100; canvas.node.height = height || 100; parent.appendChild(canvas.node); return canvas; } function init(container, width, height, fillColor) { var canvas = createCanvas(container, width, height); var ctx = canvas.context; ctx.fillCircle = function(x, y, radius, fillColor) { this.fillStyle = fillColor; this.beginPath(); this.moveTo(x, y); this.arc(x, y, radius, 0, Math.PI * 2, false); this.fill(); }; ctx.clearTo = function(fillColor) { ctx.fillStyle = fillColor; ctx.fillRect(0, 0, width, height); }; canvas.node.onmousemove = function(e) { if (!canvas.isDrawing) return; var x = e.pageX - this.offsetLeft; var y = e.pageY - this.offsetTop; var radius = 80; var fillColor = '#fff'; ctx.fillCircle(x, y, radius, fillColor); }; canvas.node.onmousedown = function(e) { canvas.isDrawing = true; }; canvas.node.onmouseup = function(e) { canvas.isDrawing = false; if (isFullyTransparent()) { redirect(); }; }; } function isFullyTransparent() { var d = canvas.context.getImageData(0, 0, canvas.node.width, canvas.node.height).data; for (var i=3; i < d.length; i+=4) if (d[i]) return false return true } function redirect() { alert('redirect') } init(document.body, 350, 350); body{ background: #000a; }

воскресенье, 9 февраля 2020 г.

Canvas / Создание эффекта (маски) миража для изображения

#javascript #svg #canvas #webgl #svg_animation


Подскажите, есть ли легкий способ создать эффекта миража(марева) на технологиях canvas
/ svg для изображения. В приведенном примере используется перемещение текстур, можно
ли проще, к примеру для анимации эффекта огня?  
    


Ответы

Ответ 1



Выбираем картинку с объемными объектами на дальнем плане, чтобы эффект миража лучше проявился. Применение фильтров сильно нагружает видеокарту, поэтому при реальном применении этого эффекта старайтесь уменьшить размеры зоны действия фильтра. Применяем к ней два фильтра feTurbulenceи feDisplacementMap Подробно о фильтрах, на русском, с многочисленными примерами можно почитать здесь За основу создания эффекта миража в фильтре отвечают несколько параметров, но основной параметр это baseFrequency Частота генерации шума, которая представляется в виде двух чисел, определяющих горизонтальное и вертикальное направления. Если задается одно число, то оно по умолчанию принимается для обоих направлений. Вот этот параметр фильтра и будем анимировать с помощью скрипта Остальные параметры фильтра можете регулировать по своему вкусу. scale="6" - увеличивает размер волн seed="53" - отвечает за слоистость структуры frames += 0.3 - увеличение прироста фраймов, увеличивает скорость волновых эффектов var img = document.querySelector("#displacementFilter feTurbulence"); var frames = 0; var rad = Math.PI / 180; function AnimateBaseFrequency() { //baseFrequency="0.01 .1" bfx = 0.01; bfy = 0.1; frames += 0.8 bfx += 0.01 * Math.cos(frames * rad); bfy += 0.01 * Math.sin(frames * rad); bf = bfx.toString() + ' ' + bfy.toString(); img.setAttributeNS(null, 'baseFrequency', bf); window.requestAnimationFrame(AnimateBaseFrequency); } window.requestAnimationFrame(AnimateBaseFrequency); .container { width:50%; height:50%; }
Пламя Чтобы получить тот или иной эффект нужно подобрать несколько однотипных картинок, например горящего огня и после экспериментов с изменением атрибутов фильтров и переменных скрипта пробовать их по очереди, какая картинка будет давать лучший эффект. var img = document.querySelector("#displacementFilter feTurbulence"); var frames = 0; var rad = Math.PI / 180; function AnimateBaseFrequency() { //baseFrequency="0.01 .1" bfx = 0.1; bfy = 0.1; frames += .4 bfx += 0.05 * Math.cos(frames * rad); bfy += 0.1 * Math.sin(frames * rad); bf = bfx.toString() + ' ' + bfy.toString(); img.setAttributeNS(null, 'baseFrequency', bf); window.requestAnimationFrame(AnimateBaseFrequency); } window.requestAnimationFrame(AnimateBaseFrequency); .container { width:50%; height:50%; }
Огонь и вода При обработке изображений необходимо уменьшить их размеры в любом растровом редакторе. Лучше в фотошопе, у которого есть недокументированная возможность уменьшать поэтапно на 10% размеры изображения без заметной потери качества. таким образом можно получить из изображения весом 2.2 Mb картинку весом 100kb В шапке svg файла необходимо выставить размер viewBox с атрибутами равными размеру используемой картинки. Например в этом примере - viewBox="0 0 670 526" var img = document.querySelector("#displacementFilter feTurbulence"); var frames = 0; var rad = Math.PI / 180; function AnimateBaseFrequency() { //baseFrequency="0.01 .1" bfx = 0.1; bfy = 0.1; frames += .31 bfx += 0.1 * Math.cos(frames * rad); bfy += 0.1 * Math.sin(frames * rad); bf = bfx.toString() + ' ' + bfy.toString(); img.setAttributeNS(null, 'baseFrequency', bf); window.requestAnimationFrame(AnimateBaseFrequency); } window.requestAnimationFrame(AnimateBaseFrequency); .container { width:50%; height:50%;


Ответ 2



Вот смастерил WebGL версию такого фильтра, за основу взят код вот из этого поста, там я уже делал фильтр для текстуры. Алгоритм: // запоминаем текущую текстурную координату vec2 p_d = uv; // прибавляем dt (сколько прошло времени с прошлого кадра) p_d.y += t * 0.1; // берем для полученного значения текстурных координат сэмпл 2d шума // (тут могла быть текстура вместо вызова математического метода, и это будет быстрее) vec2 offset = vec2(noise(p_d * 22.)); // плавно уменьшаем это значения до 0 (0 сверху) offset *= uv.y * 0.01; // берем цвет оригинальным координатам + смещение return sample(uv.xy + offset); let t = new Date().getTime(); let url = "https://webgl2fundamentals.org/webgl/resources/images/computer-history-museum/pos-z.jpg"; let filter = webglFilter(url, ` uniform float t; float rand(vec2 n) { return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); } float noise(vec2 p){ vec2 ip = floor(p); vec2 u = fract(p); u = u*u*(3.0-2.0*u); float res = mix( mix(rand(ip),rand(ip+vec2(1.0,0.0)),u.x), mix(rand(ip+vec2(0.0,1.0)),rand(ip+vec2(1.0,1.0)),u.x),u.y); return res*res; } vec4 frag (vec2 uv) { vec2 p_d = uv; p_d.y += t * 0.1; vec2 offset = vec2(noise(p_d*22.)); offset *= uv.y * 0.01; return sample(uv.xy + offset); } `); filter.ready = function() { let c = filter.canvas; document.body.append(c); animate(); } function animate() { filter.uniform('1f', 't', (new Date().getTime() - t)/1000) .apply(); requestAnimationFrame(animate); }