Поиск по этому блогу

четверг, 7 декабря 2017 г.

Конвертируем PPT в картинку

Значит про конвертацию PDF в картинку мы поговорили, а вот про конвертацию PowerPoint слайдов в картинки мы еще не видели. По сути проект такой же как и предыдущий, структуру он будет иметь такую же, я не знаю зачем я пишу отдельную статью, наверно для удобства что бы не мешать колбасу с макаронами. В любом случае я создал два проекта на гитхабе и полезней когда функции разделены для примера в разные проекты, ну мне так кажется. 

Написание данной функции такое же как и в предыдущей статье, пришлось выкручиваться так как просмотрщиков ppt файлов особо нет, по этому приходится разделять слайды на картинки и потом собирать их в том же ViewPager. Опять же, в примере я буду получать всего одну картинку, но если захотеть то можно в цикле получать весь список слайдов и потом сохранять их и отображать в ViewPager. 

В проекте как всегда будем использовать библиотеки. Самая главная либа которая нас интересует это POI которая позволяет читать Microsoft'овские файлы, просто всемогущая библиотека. Я уже писал про нее как-то пару лет назад, там я разбирал как читать Excel файлы, как оказалось опыт 2х годичной давности пригодился. Так же у нас будет Dexter для запроса пермишена на чтение и запись и ButterKnife для удобного поиска вьюх в xml.

app/build.gradle
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'

    implementation 'org.apache.poi:poi-scratchpad:3.2-FINAL'
    implementation 'com.karumi:dexter:4.2.0'

    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}

После подключения всех либ, мы как и в предыдущем проекте должны создать ImageFilePathUtils, что бы не постить его во второй раз я просто оставлю ссылку на него на GitHub, все равно все исходники доступны там.

Этот класс нам нужен для удобного преобразования ссылки на файл полученный в onActivityResult() после того как мы достанем его из файлового менеджера.

Дальше нам нужно создать AsyncTask который будет конвертировать файл. Опять же как и в предыдущем проекте я не буду использовать Rx потому что простота тут на первом месте, по этому кому надо тот переделает этот AsynkTask в вид RxAndroid'a.

PPTToImageTask.java
import android.content.Context;
import android.os.AsyncTask;

import org.apache.poi.hslf.HSLFSlideShow;
import org.apache.poi.hslf.usermodel.PictureData;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class PPTToImageTask extends AsyncTask<String, Integer, String> {

    private Context context;
    private OnFileWasConvertedListener onFileWasConvertedListener;

    public PPTToImageTask(Context context) {
        this.context = context;
    }

    @Override
    protected String doInBackground(String... strings) {
        HSLFSlideShow ppt = null;
        FileOutputStream out = null;
        File newFile = getCacheDir(context);;
        try {
            final String pathToFile = strings[0];
            final String extension = pathToFile.substring(pathToFile.lastIndexOf("."));
            if (extension.toLowerCase().equals(".ppt")) {
                POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(pathToFile));
                ppt = new HSLFSlideShow(fs);
                PictureData[] pdata = ppt.getPictures();
                PictureData pict = pdata[0];
                byte[] data = pict.getData();
                if (newFile.exists()) {
                    newFile.delete();
                }
                out = new FileOutputStream(newFile);
                out.write(data);
                out.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return newFile != null ? newFile.getAbsolutePath() : null;
    }

    @Override
    protected void onPostExecute(String result) {
        if (result != null) {
            File file = new File(result);
            if (file.exists()) {
                onFileWasConvertedListener.onFileWasConverted(file.getAbsolutePath());
            }
        }
    }

    public void setOnFileWasConvertedListener(OnFileWasConvertedListener onFileWasConvertedListener) {
        this.onFileWasConvertedListener = onFileWasConvertedListener;
    }

    private File getCacheDir(Context context) {
        return context.getCacheDir();
    }

    public interface OnFileWasConvertedListener {
        void onFileWasConverted(String path);
    }
}

В этом классе нас интересует метод doInBackground() в котором происходит вся конвертация. В нем мы получаем ссылку на файл дальше после проверок расширения файла и создания пустого для записи мы переходим к разбиванию файла с помощью класса POIFSFileSystem в который мы передали ссылку на файл, дальше методов getPictures() мы получили массив картинок со слайдера и с помощью не сложных махинаций pdata[0] получили первый слайд, ну и дальше сохраняем это все в файл — картинку и передаем ссылку на этот файл в колбек который возвращает эту ссылку в activity.

Дальше в активити мы просто отображаем то, что у нас вышло в конвертации.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical|center_horizontal">

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onImageClick"
            app:srcCompat="@mipmap/ic_launcher" />

        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="gone" />
    </RelativeLayout>

</LinearLayout>

Разметка у нас будет выглядеть так же как и в предыдущей статье, у нас будет картинка и прогресс бар который будет отображать прогресс конвертации. Ну и картинка по нажатию на которую у нас будет открываться выбор файла, и после выбора в ней будем отображать собственно то, что у нас получилось.

MainActivity будет выглядеть точь в точь так же как и в предыдущей статье, отличаться будет только метод onActivityResult() в котором вместо PDFToImageTask будет вызываться PPTToImageTask.

MainActivity.java
import android.Manifest;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;

import com.karumi.dexter.Dexter;
import com.karumi.dexter.MultiplePermissionsReport;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.multi.MultiplePermissionsListener;

import java.io.File;
import java.util.List;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import project.dajver.com.ppttoimage.task.PPTToImageTask;
import project.dajver.com.ppttoimage.task.utils.ImageFilePathUtils;

public class MainActivity extends AppCompatActivity implements PPTToImageTask.OnFileWasConvertedListener,
        MultiplePermissionsListener {

    @BindView(R.id.image)
    ImageView imageView;
    @BindView(R.id.progressBar)
    ProgressBar progressBar;

    private int PICKFILE_REQUEST_CODE = 1213;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        Dexter.withActivity(this)
                .withPermissions(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .withListener(this)
                .check();
    }

    @OnClick(R.id.image)
    public void onImageClick() {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("file/*");
        startActivityForResult(intent, PICKFILE_REQUEST_CODE);
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(resultCode == RESULT_OK) {
            progressBar.setVisibility(View.VISIBLE);
            String fPath = ImageFilePathUtils.getPath(this, data.getData());

            PPTToImageTask pdfToImageTask = new PPTToImageTask(this);
            pdfToImageTask.setOnFileWasConvertedListener(this);
            pdfToImageTask.execute(fPath);
        }
        super.onActivityResult(requestCode, resultCode, data);

    }

    @Override
    public void onFileWasConverted(String path) {
        progressBar.setVisibility(View.GONE);
        Bitmap myBitmap = BitmapFactory.decodeFile(new File(path).getAbsolutePath());
        imageView.setImageBitmap(myBitmap);
    }

    @Override
    public void onPermissionsChecked(MultiplePermissionsReport report) { }

    @Override
    public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) { }
}

Здесь у нас в onCreate() мы инициализируем ButterKnife для удобной работы с вьюхами и инициализируем Dexter для удобства создания запроса на доступ к чтению и записи файлов. Дальше у нас вызывается по клику на картинку метод onImageClick(), который открывает доступный файловый менеджер. После выбора файла у нас вызывается onActivityResult() в котором у нас идет вызов PPTToImageTask для конвертации файла в картинку и отображение прогресс бара пока картинка будет конвертироваться. И в конце вызывается onFileWasConverted() который отображает нашу картинку в ImageView и прячет прогресс бар.

Как и в предыдущей статье нам нужны разрешения на доступ в манифесте, по этому не забываем добавить их.

AndroidManifest.xml
...

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="replace" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" tools:node="replace" />

...

Я честно не знаю почему я разбил это все на две статьи, но раз уж на то пошло пусть так и будет, возможно это даже к лучшему. Дальше по идее компилируем проект и все должно быть нормально, нажимаем на иконку приложения по центру экрана, выбираем файл в появившемся окне выбора и ждем когда появится картинка. Радуемся.

Исходники:
GitHub