6

I'm just looking to see if anyone can point me in the right direction to learning how to accomplish this using Glide...

  • We have a page with content.

  • Content is displayed as a single image (think: image of magazine page)

  • In easy reading mode, I want to center and zoom on the first block of text content and mask the rest

  • When 'next' is clicked, I want to move to the next block of text content, recenter and rezoom

  • When 'back' is clicked, I want to move to the previous block of text content, recenter and rezoom

  • the mask will always been rectangular but the size will constantly change to fit the content

I've put a couple of simple images below to show what I mean. We're currently doing this with an imageview and 4 black views that we position but it's very janky and prone to misalignments. Can we accomplish this in Glide?

Thanks all!

"Full Page" View "Easy Reading" View

Psest328
  • 6,575
  • 11
  • 55
  • 90
  • 1
    You said "Content is displayed as a single image and that you want to center and zoom on each block of text content", but how are you locating blocks of text content inside an image? – Brainnovo Aug 03 '19 at 07:13

3 Answers3

6

Zoom and Mask function can be achieve in two ways. For masking the image use Glide and for zoom use PhotoView but as you mentioned that text content need to be zoom automatically then its complicated with the method you are following.

Glide gives you a functionality that you automatically limits the size of the image it holds in cache and memory to the ImageView dimensions like if the image should not be automatically fitted to the ImageView, call override(horizontalSize, verticalSize). This will resize the image before displaying it in the ImageView.

GlideApp  
  .with(context)
  .load("Image resource")
  .override(600, 200) // resizes the image to these dimensions (in pixel). resize does not respect aspect ratio
  .into(imageView);

This option might also be helpful when you load images when there is no target view with known dimension yet. For example, if the app wants to warm up the cache in the splash screen, it can't measure the ImageViews yet. However, if you know how large the images should be, use override() to provide a specific size. Check the other functions of Glide from here

Use PhotoView for zoom in and zoom out and even it's best for working in your scenario (magazine page). It can be attain by passing photoview instance as argument in glide .into() function

Also check this example

Ali Azaz Alam
  • 1,782
  • 1
  • 16
  • 27
  • This is just resizing the image to centre and zoom but how will mask and text recognition be achieved from Glide? – Sachin Kasaraddi Aug 06 '19 at 11:15
  • text recognition isn't really an issue. The page data contains tho coordinates for each individual section. I need to crop everything outside of those coordinates and zoom on the content inside them – Psest328 Aug 06 '19 at 12:25
  • @aliazazalam how would I crop and zoom with glide rather than resize? – Psest328 Aug 06 '19 at 12:26
  • 2
    both the processes can be achieved from separate libraries as I mentioned links in post like for zoom you can use PhotoView but it gives you pinch zoom functionality and for crop use glide cropping functions – Ali Azaz Alam Aug 07 '19 at 12:18
3

Use Text recognition API Mobile Vision to detect text in image and it results in Text structure. From which you can derive

  • Block is a contiguous set of text lines, such as a paragraph or column,
  • Line is a contiguous set of words on the same vertical axis, and
  • Word is a contiguous set of alphanumeric characters on the same vertical axis.

Center and zoom on the first block of text content and mask the rest using Bounding box

You can refer to this example

Sachin Kasaraddi
  • 597
  • 5
  • 12
  • text recognition isn't really an issue. The page data contains tho coordinates for each individual section. I need to crop everything outside of those coordinates and zoom on the content inside them – Psest328 Aug 06 '19 at 12:25
1

You can try the following:

In order to simulate your situation, I had to create an image with text
blocks whose coordinates and dimensions are pre-defined, so I did the following:

1) Created a simple relative layout with three text views:

<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:id="@+id/rl"
android:background="@android:color/black"
android:layout_height="match_parent">

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textAlignment="center"
    android:padding="40dp"
    android:layout_marginTop="50dp"
    android:layout_marginBottom="10dp"
    android:layout_alignParentEnd="true"
    android:layout_alignParentRight="true"
    android:text="Text Block 1"
    android:textSize="30sp"
    android:textColor="@android:color/white"
    android:id="@+id/tv1"/>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textAlignment="center"
    android:padding="40dp"
    android:layout_marginBottom="20dp"
    android:layout_alignParentStart="true"
    android:layout_alignParentLeft="true"
    android:layout_below="@id/tv1"
    android:text="Text Block 2"
    android:textSize="30sp"
    android:textColor="@android:color/white"
    android:id="@+id/tv2"/>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textAlignment="center"
    android:padding="40dp"
    android:layout_marginBottom="40dp"
    android:layout_alignParentEnd="true"
    android:layout_alignParentRight="true"
    android:layout_below="@id/tv2"
    android:text="Text Block 3"
    android:textSize="30sp"
    android:textColor="@android:color/white"
    android:id="@+id/tv3"/>

</RelativeLayout>

2) Created the activity below:

public class TextBlockActivity extends AppCompatActivity {

private final String TAG = TextBlockActivity.class.getSimpleName();
private RelativeLayout rl;
private Map<String, float[]> text_map = new HashMap<>();

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

    rl = (RelativeLayout) findViewById(R.id.rl);
    rl.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            rl.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            for (int i = 0; i < rl.getChildCount(); i++) {
                View child = rl.getChildAt(i);
                if (child instanceof TextView) {
                    TextView tv = (TextView) child;
                    float[] c = new float[]{tv.getX(), tv.getY(), tv.getWidth(), tv.getHeight()};
                    Log.i(TAG, (i + 1) + " x: " + c[0] + " y: " + c[1] + " w: " + c[2] + " h: " + c[3]);
                }
            }
        }
    });
}

}

3) Built the project on a real device, ran activity, got coordinates and dimensions of each text block and took a screenshot of the screen.

Coordinates and dimensions (x,y,w,h):

  • Text Block 1: 351, 180, 729, 361
  • Text Block 2: 0, 541, 729, 361
  • Text Block 3: 351, 962, 729, 361

Screenshot:

screenshot

4) Once I had coordinates, dimensions and an image, I did the following:

-MainActivity.class:

public class MainActivity extends AppCompatActivity {

private final String TAG = MainActivity.class.getSimpleName();
private ImageView iv;
private PhotoView pv_preview;
private LinearLayout ll;
private Button b_back;
private Button b_next;
private Map<String, float[]> text_blocks_coordinates_map = new HashMap<>();

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

    iv = (ImageView) findViewById(R.id.iv);
    pv_preview = (PhotoView) findViewById(R.id.pv_preview);
    ll = (LinearLayout) findViewById(R.id.ll);
    b_back = (Button) findViewById(R.id.b_back);
    b_back.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

            if (pv_preview.getTag() != null) {

                float[] c_current = text_blocks_coordinates_map.get((String) pv_preview.getTag());

                if (c_current != null) {

                    TreeMap<Float, String> possible_targets_map = new TreeMap<>(); //Float y:coordinate, String key
                    for (Map.Entry<String, float[]> entry : text_blocks_coordinates_map.entrySet()) {
                        //do comparison based on the y coordinate only
                        //assuming that no two blocks of text will have the same y coordinate and different x coordinate (will be horizontally aligned)
                        if (entry.getValue()[1] < c_current[1]) {
                            possible_targets_map.put(entry.getValue()[1], entry.getKey());
                        }
                    }

                    //TreeMap will sort content according to their key values in decreasing order
                    // from the treeMap of possible targets, create a list containing the values of the treeMap above (which are the keys of text_blocks_coordinates_map)
                    List<String> possible_targets_list = new ArrayList<>();
                    for (Map.Entry<Float, String> entry : possible_targets_map.entrySet()) {
                        possible_targets_list.add(entry.getValue());
                    }

                    //take the last item in this possible_targets_list as key and get content from text_blocks_coordinates_map
                    if (!possible_targets_list.isEmpty()) {
                        changePreview(iv, possible_targets_list.get(possible_targets_list.size() - 1), text_blocks_coordinates_map.get(possible_targets_list.get(possible_targets_list.size() - 1)));
                    }
                }
            }
        }
    });
    b_next = (Button) findViewById(R.id.b_next);
    b_next.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

            if (pv_preview.getTag() != null) {

                float[] c_current = text_blocks_coordinates_map.get((String) pv_preview.getTag());

                if (c_current != null) {

                    TreeMap<Float, String> possible_targets_map = new TreeMap<>();
                    for (Map.Entry<String, float[]> entry : text_blocks_coordinates_map.entrySet()) {
                        //do comparison based on the y coordinate only
                        //assuming that no two blocks of text will have the same y coordinate and different x coordinate (will be horizontally aligned)
                        if (entry.getValue()[1] > c_current[1]) {
                            possible_targets_map.put(entry.getValue()[1], entry.getKey());
                        }
                    }

                    //TreeMap will sort content according to their key values in decreasing order
                    // from the treeMap of possible targets, create a list containing the values of the treeMap above (which are the keys of text_blocks_coordinates_map)
                    List<String> possible_targets_list = new ArrayList<>();
                    for (Map.Entry<Float, String> entry : possible_targets_map.entrySet()) {
                        possible_targets_list.add(entry.getValue());
                    }

                    //take the first item in this possible_targets_list as key and get content from text_blocks_coordinates_map
                    if (!possible_targets_list.isEmpty()) {
                        changePreview(iv, possible_targets_list.get(0), text_blocks_coordinates_map.get(possible_targets_list.get(0)));
                    }
                }
            }
        }
    });

    //These are the coordinates of text blocks inside the image
    text_blocks_coordinates_map.put(String.valueOf(1), new float[]{351, 180, 729, 361});
    text_blocks_coordinates_map.put(String.valueOf(2), new float[]{0, 541, 729, 361});
    text_blocks_coordinates_map.put(String.valueOf(3), new float[]{351, 962, 729, 361});

    iv.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {

            Log.e(TAG, "Touching Image View");

            float x = motionEvent.getX();
            float y = motionEvent.getY();

            for (Map.Entry<String, float[]> entry : text_blocks_coordinates_map.entrySet()) {
                float[] c = entry.getValue();
                if (x > c[0] && x < (c[0] + c[2])
                        && y > c[1] && y < (c[1] + c[3])) {

                    changePreview(iv, entry.getKey(), entry.getValue());

                }
            }
            return false;
        }
    });
}

@Override
public void onBackPressed() {
    if (pv_preview.getVisibility() == View.VISIBLE) {
        iv.setVisibility(View.VISIBLE);
        ll.setVisibility(View.GONE);
        pv_preview.setVisibility(View.GONE);
    } else {
        super.onBackPressed();
    }
}

private void changePreview(ImageView iv, String k, float[] c) {

    //The only method that works efficiently,
    // but it is deprecated
    iv.setDrawingCacheEnabled(true);
    iv.buildDrawingCache();
    Bitmap bitmap = Bitmap.createBitmap(iv.getDrawingCache());
    iv.destroyDrawingCache();

    Bitmap resource = Bitmap.createBitmap(bitmap, Math.round(c[0]), Math.round(c[1]), Math.round(c[2]), Math.round(c[3]));
    pv_preview.setImageBitmap(resource);

    iv.setVisibility(View.GONE);
    ll.setVisibility(View.VISIBLE);
    pv_preview.setVisibility(View.VISIBLE);

    //set pv_preview tag as the key of text block content currently
    // previewed (will be used inside next and back)
    pv_preview.setTag(k);


}

}

-activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:background="@android:color/black"
android:layout_height="match_parent">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="20dp"
    android:id="@+id/ll"
    android:visibility="gone"
    android:weightSum="100">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/b_back"
        android:gravity="center"
        android:text="Back"
        android:layout_weight="50"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/b_next"
        android:gravity="center"
        android:text="Next"
        android:layout_weight="50"/>

</LinearLayout>

<ImageView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/iv"
    tools:ignore="contentDescription"
    android:src="@drawable/image"/>

<com.github.chrisbanes.photoview.PhotoView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="gone"
    android:layout_below="@id/ll"
    android:id="@+id/pv_preview"/>

   </RelativeLayout>

-Result:

result

Brainnovo
  • 1,749
  • 2
  • 12
  • 17
  • 1
    this is amazing. Unfortunately it doesn't fit this SPECIFIC use case but I can see me using this in the future. I wish I could do more than just upvote but Ali's help gets the check – Psest328 Aug 07 '19 at 12:20