Zanam
Zanam

Reputation: 4807

Downloading picture with text in ReactJS

I created a react app for a tutorial and it writes text over an uploaded image. The user after image upload types in Top Box and Bottom Box but when I try to save the image after the user has typed text, no text shows up. Can anyone please help as I want the image to be downloaded with text.

The files are Header.js with code:

import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import CardMedia from '@material-ui/core/CardMedia';
import { withStyles } from '@material-ui/core/styles';
import CardActionArea from '@material-ui/core/CardActionArea';
import { Card, CardContent } from "@material-ui/core";
import IconButton from '@material-ui/core/IconButton';
import PhotoCamera from '@material-ui/icons/PhotoCamera';

const styles = theme => ({
  media: {
      display: 'flex',
      height: 400,
      objectFit: 'contain',
      alignItems: 'center',
  },
})

class Header extends Component {
  render() {
    const {classes} = this.props;
    return(
      <Card >
        <CardContent >
          <div style={{position:'relative'}}>
            <CardMedia
              component="img"
              image = {this.props.imageIn}
            />
            <div style={{position: 'absolute', color: 'white', top: '5%', left: '50%', transform:'translateX(-50%)'}}>
              {this.props.topText}
            </div>

            <div style={{position: 'absolute', color: 'white', bottom: '5%', left: '50%', transform:'translateX(-50%)'}}>
              {this.props.bottomText}
            </div>
          </div>
        </CardContent>
      </Card>

    );
  }
}

export default withStyles(styles)(Header)

and App.js as:

import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import CardMedia from '@material-ui/core/CardMedia';
import { withStyles } from '@material-ui/core/styles';
import CardActionArea from '@material-ui/core/CardActionArea';
import { Card, CardContent } from "@material-ui/core";
import Header from './Header';
import Grid from '@material-ui/core/Grid';
import TextField from '@material-ui/core/TextField';
import IconButton from '@material-ui/core/IconButton';
import PhotoCamera from '@material-ui/icons/PhotoCamera';

const styles = (theme) => ({
  input: {
      display: 'none'
  }
});

class App extends Component{
  state = {
    topText: '',
    bottomText: '',
    randomImg: "http://i.imgflip.com/1bij.jpg",
    allImages: [],
    images: []
  }  

  handleChange = (event) => {
    const {name, value} = event.target
    this.setState({ [name]: value })
  }

  handleCapture = ({ target }) => {
    const fileReader = new FileReader();
    const name = target.accept.includes('image') ? 'images' : 'videos';

    fileReader.readAsDataURL(target.files[0]);
    fileReader.onload = (e) => {
        this.setState((prevState) => ({
            [name]: [...prevState[name], e.target.result]
        }));
    };
};

  render() {
    return(
      <div>
        <input
          accept="image/*"
          style={{display: 'none'}}
          id="icon-button-photo"
          onChange={this.handleCapture}
          type="file"
        />
        <label htmlFor="icon-button-photo">
          <IconButton color="primary" component="span">
              <PhotoCamera />
          </IconButton>
        </label>

        <Grid container justify="left" spacing={8}>
          <Grid item xs={12} sm={6}>
            <TextField
              id="standard-name"
              label="Top Text"
              name = "topText"
              value={this.state.topText}
              onChange={this.handleChange}
              margin="normal"
              variant="filled"
            />
          </Grid>

          <Grid item xs={12} sm={6}>
            <TextField
              id="standard-name"
              label="Bottom Text"
              name = "bottomText"
              value={this.state.bottomText}
              onChange={this.handleChange}
              margin="normal"
              variant="filled"
            />
          </Grid>
          <Header topText = {this.state.topText} bottomText = {this.state.bottomText} imageIn = {this.state.images}/>
        </Grid>
      </div>      
    )
  }
}

export default App

Upvotes: 7

Views: 4200

Answers (3)

Mehmet Kaplan
Mehmet Kaplan

Reputation: 2352

In your code the texts are distinct elements (childs of the div's in below code):

            <CardMedia
              component="img"
              image = {this.props.imageIn}
            />
            <div style={{position: 'absolute', color: 'white', top: '5%', left: '50%', transform:'translateX(-50%)'}}>
              {this.props.topText}
            </div>

            <div style={{position: 'absolute', color: 'white', bottom: '5%', left: '50%', transform:'translateX(-50%)'}}>
              {this.props.bottomText}
            </div>
          </div>

Rather you may try to incorporate the text into image like:

    const canvas = this.refs.canvas;
    const ctx = canvas.getContext("2d");
    const img = this.refs.image;

    img.onload = () => {
      ctx.drawImage(img, 0, 0);
      ctx.font = "40px Courier";
      ctx.fillText(this.props.text, 210, 75); // THIS IS THE PLACE TEXT IS EMBEDDED INTO THE PICTURE
    };

In this code sandbox link, https://codesandbox.io/s/blazing-sun-e2bgf , you can find a simple class named Canvas which uses above method so that when the file is saved you can see the text.

One catch here, the example I provided is free from material UI. You are using CardMedia class which takes the image and shows it. But in the example I provided, the method is generic. So you can place the image seperately in the card.


Credit: https://blog.cloudboost.io/using-html5-canvas-with-react-ff7d93f5dc76

Upvotes: 6

SylvainAr
SylvainAr

Reputation: 154

What you're asking is not possible with a DOM manipulation library such as React. You're trying to do some image edition and rendering in a browser with a framework that is made to handle DOM.

You can use React for the "preview", but when it's time to actually render the image, you'll need a tool such as https://imagemagick.org/index.php. You can find the node binding here : https://www.npmjs.com/package/gm

Upvotes: 0

Cmcco91
Cmcco91

Reputation: 99

I’m fairly new to it all but if you you want to lay text over the image you may need to use the z index. Z-index: 1; would be the back div (photo or image) Z-index:2; would be the would be the div with the text over the top. Aswell as alpha opacity to set the div with the text to transparent.

And using absolute position will ensure they are both positioned on top of each other.

Upvotes: 0

Related Questions